Pulse
Back
API reference

Build on Pulse

Every entry, project, category, and work memory in your workspace is reachable two ways: a 13-tool MCP server for AI agents (20 with work memory switched on, 22 with Jev review of captures also on), and a REST API under /api/v1 for scripts, cron jobs, and dashboards. Both are thin wrappers over the same data layer, so reads and writes stay consistent whichever one you use.

Authentication

Every request — MCP or REST — carries a bearer token. Create one from Settings → Developers once signed in.

Authorization: Bearer psk_...
REST accepts only a psk_... API token.
MCP accepts a psk_... token or an OAuth 2.1 access token — used for the Claude.ai custom connector, which authorizes over OAuth instead of a pasted token.
A token always resolves to one user. Every read and write is scoped to that user’s own data — an admin token can additionally see workspace-wide catalogs (members) where noted below.

MCP server

A remote, streamable-HTTP MCP server. Point any MCP client — Claude Code, Codex, or Claude.ai as a custom connector — at:

{ "pulse": { "url": "https://pulse.transmit.work/api/mcp", "headers": { "Authorization": "Bearer psk_..." } } }
pulse_whoami
Read-only
Return the authenticated user’s profile.
No parameters.
Returns Profile: id, name, email, role, team, experience, companyId, emailVerified, tools[].
pulse_get_plan
Read-only
The workspace’s plan and seat usage. Pending invitations count as occupied seats.
No parameters.
Returns { billingEnabled, plan, source, seats: { limit, occupied, active, pending, available }, recording: { restricted, userId }, projects: { limit, active }, capabilities, inGrace, graceEndsAt, freePeriodEndsAt, alwaysFree[], proOnly[] }. On the Free plan only the selected recording member can log or edit time; everyone else is view/export only. Writes that a plan doesn’t allow fail with 402 and a message starting “Plan limit:”.
pulse_list_entries
Read-only
List the caller’s own time entries, newest first, paginated.
Parameters
scope · "week" | "month" | "last_month"
Reporting period. Defaults to the current week (Mon–Sun).
limit · number
Max entries to return, 1–200. Default 50.
offset · number
Entries to skip, for pagination. Default 0.
Returns { scope, total, count, offset, limit, has_more, next_offset, items[] } — each item has id, date, category, categoryLabel, tool, project, description, start, end, durationMin, pauseMin, kind, source, note.
pulse_get_summary
Read-only
The caller’s balance for the period: minutes used vs. quota.
Parameters
scope · "week" | "month" | "last_month"
Reporting period. Defaults to the current week.
Returns { scope, usedMin, quotaMin, percentUsed, remainingMin, entryCount, timeUnit, categories[] } — categories[] is a per-category minutes breakdown, sorted descending.
pulse_list_categories
Read-only
List the workspace’s active activity categories.
No parameters.
Returns { items: [{ id, label, color }] }
pulse_update_category
WriteAdmin-only
Rename an activity category.
Parameters
category · string · required
Category id, or exact (case-insensitive) label.
label · string · required
New label.
Returns { ok: true, id, message }
pulse_list_tools
Read-only
List the tools/sources assigned to the caller.
No parameters.
Returns { items: [{ id, label, purpose, domain }] } — only tools assigned to the caller.
pulse_update_tool
WriteAdmin-only
Rename a tool/source.
Parameters
tool · string · required
Tool id, or exact (case-insensitive) label.
label · string · required
New label.
Returns { ok: true, id, message }
pulse_list_projects
Read-only
List the projects assigned to the caller.
No parameters.
Returns { items: [{ id, name }] } — only projects assigned to the caller.
pulse_create_project
WriteAdmin-only
Create a new project on the workspace.
Parameters
name · string · required
Project name (e.g. "Migration"). Appended to the end of the active list.
Returns { ok: true, id, message } on success, or { isError: true } with a plain-text reason.
pulse_update_project
WriteAdmin-only
Rename a project. There’s no archive/delete tool for projects (or categories, or tools) — that’s an intentional, in-app-only action in Settings, not something an agent can do in one call.
Parameters
project · string · required
Project id, or exact (case-insensitive) name.
name · string
New name. Only fields you pass are changed.
Returns { ok: true, id, message }
pulse_list_members
Read-onlyAdmin-only
List workspace members.
No parameters.
Returns { items: [{ id, name, email, role, team, pending, emailVerified }] }. Errors for non-admin tokens.
pulse_log_entry
Write
Log a time entry on the caller’s own timesheet. Recorded directly and immediately visible — no approval step. Entries logged this way are tagged source: "agent".
Parameters
category · string · required
Prefer the readable label (e.g. "Dev") from pulse_list_categories — the id also works.
tool · string · required
Prefer the readable label (e.g. "Claude Code") from pulse_list_tools — the id also works.
description · string · required
What the work was.
durationMin · number · required
Duration in minutes.
date · string · required
YYYY-MM-DD — the day the work happened. Not defaulted, so a backfilled entry never gets silently dated to today.
start · string
Optional start time, HH:MM.
end · string
Optional end time, HH:MM.
note · string
Optional free-text note — e.g. a PR URL or issue id, for traceability.
project · string
Project label or id. Sets project attribution in the same call.
reviewId · string
If this row was reviewed with pulse_review_captures, its reviewId — records that the user approved it (and whether they edited it first) for calibration. Never affects whether the entry is logged.
Returns { ok: true, id, message: "Entry logged." }
pulse_update_entry
Write
Update fields on an existing entry owned by the caller. Only the fields you pass change.
Parameters
entry_id · string · required
The entry id, from pulse_list_entries.
category · string
New category — label or id.
tool · string
New tool — label or id.
project · string | null
New project — label or id; pass null to clear.
description · string
New description.
durationMin · number
New duration in minutes.
date · string
YYYY-MM-DD.
start · string | null
HH:MM, or null to clear.
end · string | null
HH:MM, or null to clear.
note · string | null
Or null to clear.
Returns { ok: true, id, message: "Entry updated." }
The two tools below are opt-in. An admin turns on Jev review under Settings → Lab — off by default. With it off, these tools don’t appear in tools/list at all, and pulse_review_captures returns { skipped: true, reason } rather than an error if called anyway (e.g. right after an admin turns it back off mid-session).
pulse_review_captures
Write
Get Jev’s independent, advisory read on a batch of time entries before showing them to the user for approval — before any of them are logged. This call writes nothing to the timesheet. Show the result next to each proposed row, then still wait for the user’s explicit approval — a recommendation: "approve" is advice, not approval. Pass the row’s reviewId to pulse_log_entry for rows the user approves, and report the rest via pulse_record_capture_decisions.
Parameters
entries · object[] · required
Up to 30 proposed time entries, not yet logged: date, durationMin, category, tool, description are required; start, end, project, note are optional. Each may also carry an evidence object — { commits?, prs?, repos?, sessions? } — with whatever real commit shas, PR URLs, repo names, or session titles/minutes back the row. Never invent evidence that isn’t there; an entry with none is scored on the row’s own content alone.
Returns { reviewer: "jev", advisory: true, note, items }. Each item: { index, reviewId, status: "ok" | "error", recommendation?, confidence?, summary?, issues?, error? }. index matches the position in the request’s entries array. recommendation is approve | review | reject; issues is up to a few { type, severity, message } objects. An item can come back status: "error" on its own (the evaluator failed for that row) without failing the whole call.
pulse_record_capture_decisions
Write
Report the rows a user dropped after reviewing proposed entries, so Pulse can measure how well Jev’s advice matched what the user actually decided (see scripts/review-metrics.ts in the repo). Approved rows are recorded automatically when their reviewId is passed to pulse_log_entry — this tool is only for the ones that weren’t. Changes no entries.
Parameters
dropped · string[] · required
reviewIds (from pulse_review_captures) of rows the user chose not to log — up to 30.
Returns { recorded: number } — how many of the reviewIds matched a review the caller owns that hadn’t already been closed by a decision.
The seven work-memory tools below are opt-in, and scoped by role. They’re off for new workspaces. An admin enables the feature under Settings → Lab → Work memory (Off / Admins only / Everyone), and sets separately, under Agent capture, whose agents may use these tools — so a workspace can let every engineer’s agent file suggestions while only admins review them. While a token’s role is excluded the tools don’t appear in tools/list at all (rather than appearing and failing), and the REST endpoints return 403 — or 404 when the feature is off for the whole workspace. If your agent can’t see them, those settings are why.
pulse_suggest_memory
Write
Propose something durable the agent noticed while working — a decision made, a constraint discovered, a migration completed. It lands as status: "suggested" for a human to confirm, edit, or dismiss in the app, so it is safe to offer one when unsure. Routine activity (typo fixes, running tests, ordinary commits) should never produce a memory — the timeline is already exhaustive; memory stays selective.
Parameters
type · string · required
decision | learning | outcome | milestone | blocker.
title · string · required
The decision or lesson itself, stated plainly.
summary · string · required
A sentence or two that still makes sense out of context.
detail · string
Longer context — alternatives weighed, caveats, links.
occurredOn · string · required
YYYY-MM-DD — the day it happened.
project · string
Project name or id. Omit for a workspace-level memory.
confidence · number
0-1 or 0-100.
entryIds · string[]
Ids of the caller’s own entries this came from, for provenance.
source · object
{ repo?, path, commit } — a note in a connected GitHub repo (see pulse_get_memory_repo). commit must be the full 40-character sha. Pulse checks the file exists at that commit and stores only the pointer, never the note. If GitHub can’t be reached the memory is still saved, marked unverified; a definite “not found” is rejected. Use this or sourceUrl, not both.
agent · string
Optional, self-reported name of the agent (e.g. “Claude Code”), up to 64 characters. Shown to reviewers as unverified.
model · string
Optional, self-reported model name, up to 64 characters. Unverified.
sessionId · string
Optional session or conversation id, up to 128 characters, so a reviewer can group memories from one session. Unverified.
Returns { ok: true, id, status: "suggested", message }
pulse_create_memory
Write
Save a memory directly as confirmed. Only for when the user explicitly asked to remember something (“remember that we chose Vercel”) — an inference the agent made on its own belongs in pulse_suggest_memory.
Parameters
Same as pulse_suggest_memory, minus confidence.
Returns { ok: true, id, status: "confirmed", message }
pulse_list_memories
Read-only
The workspace’s durable knowledge, newest first. Returns confirmed memories by default, so an agent reading back gets settled decisions rather than untriaged guesses. Only confirmed memories are workspace-wide; suggestions stay visible to whoever captured them (and admins).
Parameters
status · string
confirmed (default) | suggested | dismissed | any.
type · string
Filter to one kind.
project · string
Project name or id.
from · string
Earliest occurredOn (YYYY-MM-DD).
to · string
Latest occurredOn (YYYY-MM-DD).
credential · string
Only memories written with this credential id (createdBy.agent.verified.credentialId).
agent · string
Only memories whose self-reported agent name matches (case-insensitive). Unverified.
limit · number
Default 50, max 200.
offset · number
For pagination.
Returns { total, count, offset, limit, has_more, next_offset, items } — each item carries its provenance: type, title, summary, detail, status, confidence, occurredOn, project, createdBy { userId, name, source, agent }, and entryIds. For agent-written memories createdBy.agent is { verified: { via, credentialId, credentialLabel }, claimed: { name, model, sessionId } }: verified is derived by Pulse from the token that wrote it (API token or OAuth client); claimed is whatever the agent said about itself and is unverified. agent is null for memories a person wrote.
pulse_search_memories
Read-only
Search across title, summary, and detail — for questions like “what did we learn about Supabase auth?”. Keyword matching always runs. If an admin has enabled Semantic search (Settings → Lab), results are also ranked by meaning across English and Japanese and each carries a similarity score (null for keyword-only hits). Rows without an index yet stay reachable by keyword. The calling model still judges relevance.
Parameters
query · string · required
Keywords or a natural-language question. Keywords always match; meaning also matches when Semantic search is on.
status · string
any (default) | confirmed | suggested | dismissed.
type · string
Filter to one kind.
project · string
Project name or id.
from · string
Earliest occurredOn (YYYY-MM-DD).
to · string
Latest occurredOn (YYYY-MM-DD).
includeSuperseded · boolean
Also return memories a confirmed newer one has replaced. Default false.
credential · string
Only memories written with this credential id.
agent · string
Only memories whose self-reported agent name matches (case-insensitive). Unverified.
limit · number
Default 25, max 200.
offset · number
For pagination.
Returns { query, total, count, offset, limit, has_more, next_offset, items }
pulse_update_memory
Write
Refine a memory the caller captured that a human hasn’t confirmed yet. Once confirmed it is the team’s record, and only an admin can change it.
Parameters
memory_id · string · required
The memory id, from pulse_list_memories.
type · string
New kind.
title · string
New title.
summary · string
New summary.
detail · string | null
Or null to clear.
occurredOn · string
YYYY-MM-DD.
project · string | null
Name or id; null to clear.
confidence · number
0-1 or 0-100.
Returns { ok: true, id, message: "Memory updated." }
pulse_get_memory_repo
Read-only
Where this workspace keeps agent memory notes: the connected GitHub repo(s), the folder and file format to write in, and how to link a note to a suggestion. Read-only, and it returns no credentials. Agents write and push notes with git themselves; Pulse only checks that a cited file exists at a commit.
Parameters
Returns { connected, repos: [{ repo, pathPrefix, project, status }], noteFormat, permalinkTemplate, howToSuggest }
pulse_dismiss_memory
Write
Retract a suggestion that turned out to be wrong. Works only on the caller’s own memories that nobody has confirmed yet, and sets status: "dismissed" rather than deleting anything.
Parameters
memory_id · string · required
The memory id.
Returns { ok: true, id, message: "Memory dismissed." }
There is no pulse_confirm_memory — by design. Confirming is what turns an agent’s inference into organizational truth that future agents read back as settled fact, so it stays a deliberate in-app action (the same way deleting an entry does). An agent can still retract its own untriaged suggestion with pulse_dismiss_memory. In the app, only the memory’s author or an admin can confirm it. Each memory records the token or OAuth client that wrote it; the agent, model and session labels an agent may send are stored as self-reported and never used for access decisions.

No delete tool exists, by design — every entry an agent writes stays visible and editable, never silently removed.

REST API

Bearer-token endpoints under:

https://pulse.transmit.work/api/v1

REST covers the same ground as MCP: reading everything, creating and updating entries, renaming categories and tools, and listing, creating, or renaming projects. There’s no archive/delete for projects, categories, or tools on either surface — an intentional, in-app-only action in Settings.

GET/api/v1/me
Read-only
The authenticated user’s profile.
Returns Profile: id, name, email, role, team, experience, companyId, emailVerified, tools[]. 404 if not found.
GET/api/v1/plan
Read-only
The workspace’s plan and seat usage.
Returns { billingEnabled, plan, source, seats: { limit, occupied, active, pending, available }, inGrace, graceEndsAt, alwaysFree[], proOnly[] }. Plan-limited writes elsewhere answer 402 with a message starting “Plan limit:”.
GET/api/v1/entries
Read-only
List the caller’s own time entries, newest first, paginated.
Query parameters
scope · "week" | "month" | "last_month"
Defaults to the current week.
limit · number
1–200. Default 50.
offset · number
Default 0.
Returns { scope, total, count, offset, limit, has_more, next_offset, items[] }
POST/api/v1/entries
Write
Create a time entry on the caller’s own timesheet, tagged source: "api" — distinct from an entry typed into the app ("manual") or logged by an agent over MCP ("agent").
Request body (JSON)
category · string · required
Label or id.
tool · string · required
Label or id.
description · string · required
What the work was.
durationMin · number · required
Duration in minutes.
project · string
Project label or id. Sets project attribution in the same call.
date · string · required
YYYY-MM-DD — the day the work happened. Not defaulted.
start · string
HH:MM.
end · string
HH:MM.
note · string
Free-text note.
Returns 201 { ok: true, id } on success; 400 { error } on invalid input.
PATCH/api/v1/entries/:id
Write
Update fields on an existing entry owned by the caller — :id is the entry id, from GET /api/v1/entries. Only the fields you pass change, same as pulse_update_entry.
Request body (JSON)
category · string
New category — label or id.
tool · string
New tool — label or id.
project · string | null
New project — label or id; pass null to clear.
description · string
New description.
durationMin · number
New duration in minutes.
date · string
YYYY-MM-DD.
start · string | null
HH:MM, or null to clear.
end · string | null
HH:MM, or null to clear.
note · string | null
Or null to clear.
Returns { ok: true, id }; 404 if the entry doesn’t exist or isn’t the caller’s; 400 on invalid input.
GET/api/v1/summary
Read-only
The caller’s balance for the period.
Query parameters
scope · "week" | "month" | "last_month"
Defaults to the current week.
Returns { scope, usedMin, quotaMin, percentUsed, remainingMin, entryCount, timeUnit, categories[] }
GET/api/v1/categories
Read-only
The workspace’s active activity categories.
Returns { items: [{ id, label, color }] }
PATCH/api/v1/categories/:id
WriteAdmin-only
Rename an activity category — :id is the category id, from GET /api/v1/categories. Same as pulse_update_category.
Request body (JSON)
label · string · required
New label.
Returns { ok: true, id }; 404 if the category doesn’t exist; 403 for non-admins; 400 on invalid input.
GET/api/v1/tools
Read-only
The tools/sources assigned to the caller.
Returns { items: [{ id, label, purpose, domain }] } — only tools assigned to the caller.
PATCH/api/v1/tools/:id
WriteAdmin-only
Rename a tool/source — :id is the tool id, from GET /api/v1/tools. Same as pulse_update_tool.
Request body (JSON)
label · string · required
New label.
Returns { ok: true, id }; 404 if the tool doesn’t exist; 403 for non-admins; 400 on invalid input.
GET/api/v1/projects
Read-only
The projects assigned to the caller.
Returns { items: [{ id, name }] } — only projects assigned to the caller.
POST/api/v1/projects
WriteAdmin-only
Create a new project on the workspace.
Request body (JSON)
name · string · required
Project name (e.g. "Migration"). Appended to the end of the active list.
Returns 201 { ok: true, id } on success; 403 { error: "Admins only." } for non-admins; 400 { error } on invalid input.
PATCH/api/v1/projects/:id
WriteAdmin-only
Rename a project — :id is the project id, from GET /api/v1/projects. Same as pulse_update_project. No archive/delete endpoint exists — that’s an intentional, in-app-only action in Settings.
Request body (JSON)
name · string · required
New name.
Returns { ok: true, id }; 404 if the project doesn’t exist; 403 for non-admins; 400 on invalid input.
GET/api/v1/members
Read-onlyAdmin-only
Workspace members — id, name, email, role, team, pending, emailVerified.
Returns 200 { items: [...] } for admins; 403 { error: "Admins only." } otherwise.
GET/api/v1/memories
Read-only
Work memories, newest first — ?status= (confirmed by default),?type=, ?project=, ?from=, ?to=, ?credential= (verified credential id), ?agent= (self-reported name), ?limit=, ?offset=. Adding ?q= switches to search across title, summary, and detail (keyword, plus semantic when the workspace enabled it), and ?includeSuperseded=true also returns memories a newer confirmed one replaced. Mirrors pulse_list_memories / pulse_search_memories.
Returns 200 { total, count, offset, limit, has_more, next_offset, items }. With ?q= the response also carries query.
GET/api/v1/memories/repos
Read-only
The GitHub repo(s) connected for agent memory notes and the note format. Mirrors pulse_get_memory_repo. POST and PATCH /memories accept a source object { repo?, path, commit } (full 40-character sha) and answer with sourceVerified.
Returns 200 { connected, repos, noteFormat, permalinkTemplate, howToSuggest }
POST/api/v1/memories
Write
Capture a memory. Body: type, title, summary, occurredOn (all required), plus optional detail, project, confidence, entryIds, and the unverified labels agent, model, sessionId. Pulse records the calling token itself. Defaults to a suggestion; send status: "confirmed" only when the user explicitly asked to remember it.
Returns 201 { ok: true, id, status }
PATCH/api/v1/memories/:id
Write
Edit a memory you captured that nobody has confirmed yet — a key you omit stays untouched, an explicit null clears detail or project. Sending status: "dismissed" retracts it; that is the only status change available here, since confirming happens in the app.
Returns 200 { ok: true, id }; 403 when the memory isn’t yours or is already confirmed.
Full setup walkthroughs (connecting Claude Code, Codex, or the Claude.ai connector) live in the Help documentation. Something look wrong here? Tell us.