Developers
Summaries API
Reference for creating, updating, sharing, and approving employee summaries via the Performance Blocks API.
Plan availability: The Performance Blocks API is available on the Agentic plan. Team plan customers can upgrade in Settings → Billing.
A Summary is a manager's narrative review for a single employee covering a defined period. It rolls up observations and objectives, captures the manager's analysis as rich text, and moves through a status lifecycle from draft to shared. The Summaries API lets you list, create, update, share, approve, and collect feedback on summaries programmatically.
This is useful for:
- Pre-populating draft summaries from external systems (e.g. a calendar of past 1:1s, an outside review tool's notes).
- Pulling completed summaries into a data warehouse or BI dashboard.
- Triggering downstream workflows when a summary is shared (notify HRIS, archive to document storage, etc.).
Resource shape
{
"id": "sum_01HXX9YK7Z8N2P3Q4R5S6T7U8V",
"employee_id": "emp_01HXX...",
"manager_id": "emp_01HYY...",
"period_start": "2026-01-01",
"period_end": "2026-03-31",
"body": {
"type": "doc",
"content": [
{ "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "Strengths" }] },
{ "type": "paragraph", "content": [{ "type": "text", "text": "Jordan led the Q1 launch end-to-end..." }] }
]
},
"status": "draft",
"included_observation_ids": ["obs_01HXX...", "obs_01HYY..."],
"included_objective_ids": ["obj_01HXX..."],
"shared_at": null,
"approved_at": null,
"created_at": "2026-04-01T09:12:00.000Z",
"updated_at": "2026-04-12T15:42:18.000Z"
}
Field reference
| Field | Type | Notes |
|---|---|---|
id |
string | ULID prefixed with sum_. Read-only. |
employee_id |
string | The subject of the summary. |
manager_id |
string | The author. Defaults to the manager-of-record at create time. |
period_start |
string (date) | ISO 8601. Required. |
period_end |
string (date) | ISO 8601. Required. Must be on or after period_start. |
body |
object | Rich text JSON (ProseMirror-style). See below. |
status |
enum | draft, pending_review, approved, shared, archived. |
included_observation_ids |
string[] | Observations the summary explicitly cites. |
included_objective_ids |
string[] | Objectives the summary explicitly cites. |
shared_at |
string (datetime) | null | Set when the summary transitions to shared. |
approved_at |
string (datetime) | null | Set when the summary transitions to approved. |
created_at |
string (datetime) | Read-only. |
updated_at |
string (datetime) | Read-only. |
Body shape
body is a structured rich-text document. The top-level shape is:
{ "type": "doc", "content": [ /* block nodes */ ] }
Supported block nodes: paragraph, heading (level 1–3), bullet_list, ordered_list, list_item, blockquote, horizontal_rule. Inline marks: bold, italic, underline, strike, link (with href attr).
If you only need plain text, send a single paragraph:
{ "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "..." }] }] }
Status lifecycle
A summary moves through these states:
draft -> pending_review -> approved -> shared
\____________________________________/
v
archived (terminal)
| From | To | Trigger |
|---|---|---|
draft |
pending_review |
POST /summaries/{id}/submit (or app: "Submit for review"). |
draft or pending_review |
approved |
POST /summaries/{id}/approve. Only required if the org enables review workflow. |
approved or draft |
shared |
POST /summaries/{id}/share. |
| any | archived |
POST /summaries/{id}/archive. |
Once shared, the body is locked. Updates after sharing require unsharing first (admin-only via the application), or creating a new summary.
List summaries
GET /summaries
Query parameters
| Parameter | Type | Notes |
|---|---|---|
limit |
integer | Default 50, max 100. |
cursor |
string | Pagination. |
sort |
string | period_end, -period_end, created_at, -created_at. Default -period_end. |
filter[employee_id] |
string | Restrict to one employee. |
filter[manager_id] |
string | Summaries authored by this manager. |
filter[status] |
enum | One of the status values. Repeat for multiple. |
filter[period_start][gte] |
date | Inclusive. |
filter[period_end][lte] |
date | Inclusive. |
expand |
string | employee, manager, observations, objectives. |
Example: curl
curl "https://api.performanceblocks.com/v1/summaries\
?filter[status]=shared\
&filter[period_end][gte]=2026-01-01\
&limit=50" \
-H "Authorization: Bearer $PB_API_KEY"
Get a summary
GET /summaries/{id}
Returns the full summary including the body. Returns 404 if the ID is not in your organization.
curl https://api.performanceblocks.com/v1/summaries/sum_01HXX... \
-H "Authorization: Bearer $PB_API_KEY"
Create a summary
POST /summaries
Required scope: summaries:write.
Request body
{
"employee_id": "emp_01HXX...",
"manager_id": "emp_01HYY...",
"period_start": "2026-01-01",
"period_end": "2026-03-31",
"body": {
"type": "doc",
"content": [
{ "type": "paragraph", "content": [{ "type": "text", "text": "Initial draft from external tool import." }] }
]
},
"included_observation_ids": ["obs_01HXX..."],
"included_objective_ids": ["obj_01HXX..."]
}
Validation rules
employee_idmust reference an employee in your organization.manager_idis optional. If omitted, defaults to the employee's current manager.period_startandperiod_endare required.period_end >= period_start. Both must be valid ISO dates and not in the future.bodyis required and must conform to the rich-text schema above. Maximum serialized size 256 KB.included_observation_idsandincluded_objective_idsmust reference resources in your organization. Each list is capped at 200 entries.
A successful create returns 201 with status: "draft".
Example: TypeScript
const res = await fetch('https://api.performanceblocks.com/v1/summaries', {
method: 'POST',
headers: {
Authorization: `Bearer
Update a summary
PATCH /summaries/{id}
Required scope: summaries:write.
You can update body, period_start, period_end, included_observation_ids, and included_objective_ids while the status is draft or pending_review. Once approved or shared, the body is locked.
curl -X PATCH https://api.performanceblocks.com/v1/summaries/sum_01HXX... \
-H "Authorization: Bearer $PB_API_KEY" \
-H "Content-Type: application/json" \
-d '{"included_observation_ids": ["obs_01HXX...", "obs_01HZZ..."]}'
Restrictions
employee_idandmanager_idcannot be changed after creation.- Updates after
sharedreturn409 conflict.
Submit for review
POST /summaries/{id}/submit
Required scope: summaries:write. Transitions draft → pending_review. Returns 409 if the summary is not in draft.
curl -X POST https://api.performanceblocks.com/v1/summaries/sum_01HXX.../submit \
-H "Authorization: Bearer $PB_API_KEY"
Approve a summary
POST /summaries/{id}/approve
Required scope: summaries:write. Transitions pending_review (or draft, depending on org workflow) → approved. Sets approved_at. Returns 409 if the summary is not in a state that can be approved.
curl -X POST https://api.performanceblocks.com/v1/summaries/sum_01HXX.../approve \
-H "Authorization: Bearer $PB_API_KEY"
Share a summary
POST /summaries/{id}/share
Required scope: summaries:write. Transitions to shared, sets shared_at, locks the body, and notifies the employee through the standard application channels (email + in-app notification, if those are enabled in their preferences).
Optional body to customize delivery:
{
"notify_employee": true,
"message": "Hi Jordan — see the Q1 review attached. Happy to chat live tomorrow."
}
| Field | Type | Default | Notes |
|---|---|---|---|
notify_employee |
boolean | true |
Set false to share silently (e.g. when delivering via your own channel). |
message |
string | null | null |
Optional personal note included in the notification. Max 1000 chars. |
Returns the updated summary with status: "shared" and shared_at set.
Archive a summary
POST /summaries/{id}/archive
Required scope: summaries:write. Soft-deletes the summary. Excluded from default lists. Audit-preserved. There is no public restore endpoint — restoration is admin-only via the application.
Submit feedback on a summary
POST /summaries/{id}/feedback
Records employee acknowledgment or a clarification request on a shared summary.
Request body
{
"kind": "acknowledgment",
"comment": "Thanks — landed well. One follow-up question on Goal #2."
}
| Field | Type | Notes |
|---|---|---|
kind |
enum | acknowledgment or clarification_request. |
comment |
string | null | Optional. 0–2000 characters. |
The summary must be in shared status. The endpoint records the feedback and, if kind is clarification_request, surfaces it to the manager in the application.
Webhook events
| Event | Fires when |
|---|---|
summary.created |
A summary is created (draft). |
summary.updated |
Body or included IDs change. |
summary.submitted |
Transition to pending_review. |
summary.approved |
Transition to approved. |
summary.shared |
Transition to shared. |
summary.archived |
Transition to archived. |
summary.feedback_submitted |
Employee posts feedback on a shared summary. |
The delivery data field contains the post-change resource (and, for feedback_submitted, includes a feedback sub-object with kind, comment, author_id, created_at). See Webhooks.
Common error responses
| Status | Code | Cause |
|---|---|---|
| 400 | validation_error |
Missing field, invalid date range, body too large, malformed body schema. |
| 401 | authentication_required |
Missing or invalid API key. |
| 403 | permission_denied |
Key lacks summaries:write (or :read for GETs). |
| 404 | not_found |
Summary not in your organization. |
| 409 | conflict |
Status transition not allowed (e.g. share an archived summary, update a shared summary). |
| 422 | unprocessable |
Referenced employee, observation, or objective is not in your organization. |
Use cases
Pre-populating draft summaries from notes
If your team uses an external 1:1 notes tool, run a weekly job that pulls notes from the last quarter, drops them into a summary body, and creates a draft summary linked to the manager. The manager opens the application and finishes the review with a head start.
const draft = await pb.request('/summaries', {
method: 'POST',
body: JSON.stringify({
employee_id: report.employee_id,
period_start: quarter.start,
period_end: quarter.end,
body: notesToProseMirror(report.notes),
included_observation_ids: report.observation_ids
})
});
Archiving completed summaries to your DMS
Subscribe to summary.shared, fetch the summary by ID, render the body to PDF (your renderer of choice), and upload to your document management system. Track the id so you can correlate later.
Auditing summary throughput
Schedule a daily GET /summaries?filter[status]=shared&filter[period_end][gte]=... and pipe results into your warehouse. Use cursors to page through; persist the latest period_end you've ingested as a watermark.
Related
- API overview
- Observations API — observations cited by summaries.
- Objectives API — objectives cited by summaries.
- Webhooks — real-time summary events.