Pingtower Docs
Docs The service Reports

Reports

The project report routes that older clients and scripts use. A report is now an incident with a home project, so these routes read and write team incidents.

A report is an incident whose home project is the one in the path. The routes below predate incidents and keep working for older clients and scripts. They read and write the same records the web app and the iOS app show under Incidents. A report created here gets the team’s next number, such as INC-42. An incident opened elsewhere with this home project is listed here too. A team-wide incident, one with no home project, is not in any project’s list.

Writing through these routes needs a member or above, or any role with reports:write. A responder works incidents in the apps instead.

Creating one

POST /v1/projects/{id}/reports

{
  "title": "Checkout degraded, 2026-09-01",
  "description": "Gateway timeouts from 03:12 UTC. Payments retried; no orders lost.",
  "alert_id": "…",
  "view_password": "correct-horse"
}
FieldMeaning
titleUp to 256 bytes.
descriptionUp to 4096 bytes. Internal: the team sees it in the apps and the API, and no public page ever shows it.
alert_idOptional. Must name a live alert in this project. It becomes the incident’s first attached alert, unless that alert is already on another incident, where it stays.
view_passwordOptional, 8 to 72 bytes. Readers of the share link must enter it.
status_page, componentsOptional. Publish the report to status pages. Only after it has a public entry: on create, true is a 409 write a public update first: only public entries leave the team, so create it, write an update, then turn it on. components names up to 50 components, each as {"component_id": "…", "impact": "partial"} with an impact of degraded, partial or major. A component must be bound to a check or rule in the report’s own project; one with no bindings can be picked only by a member not limited to projects. Any other is a 400 component <id> is not bound to project <P>.

The response carries id and public_uuid. The incident opens in state open. Unlike an incident opened in the apps, its share link at /public/reports/{uuid} works from the start. A project holds at most 200 reports, counting every incident whose home project it is, and past that the create answers 429.

GET /v1/projects/{id}/reports lists the project’s reports, newest first, each with password_protected. GET /v1/projects/{id}/reports/{rid} reads one with its entries and attached alerts. Both carry the incident’s own fields, such as number, reference, state, close_reason, severity, lead, cause, fix and started_at, and the status page fields status_page, components and stage. Each entry carries its visibility and stage.

PUT /v1/projects/{id}/reports/{rid} replaces the title and description. view_password there is tri-state: omitted keeps it, "" clears it, a value replaces it. status_page and components are kept when omitted, and components, when sent, replaces the whole list. Turning status_page on is refused with 409 until the report has a public entry. Setting a password takes the report off every status page.

Entries

Entries are the timeline. Each is Markdown (CommonMark, plus GitHub tables, strikethrough, autolinks and task lists), rendered once to sanitized HTML at write time. Lists and quotes nested more than 16 deep on one line stay plain text, as do tables wider than 64 columns or past 10,000 cells in one entry, and link, image, emphasis, strikethrough and code-span markers past a per-paragraph budget that only very long, marker-packed paragraphs reach. An entry whose rendered HTML would pass 1 MiB is refused with 400 on content_md (rule rendered_size) and is not stored. Every entry written through these routes is public. It shows on the share link and, if the report is published, on status pages. Write internal notes in the apps.

POST /v1/projects/{id}/reports/{rid}/entries

{ "title": "Root cause", "content_md": "Upstream gateway rotated its TLS cert …", "entry_type": "analysis", "stage": "identified" }

entry_type is update, analysis or resolution, default update. stage is optional: investigating, identified, monitoring or resolved. A resolution entry, or one with stage resolved, resolves the incident with no cause or fix recorded, so it shows as cause not recorded until someone adds them in the apps. PUT and DELETE …/entries/{eid} edit and remove an entry. A report holds at most 1000 entries.

GET /public/reports/{uuid} renders the report’s title and its public entries without any credential. The description and internal notes stay internal, so write what readers should see as an entry. A password-protected report shows a prompt, and POST /public/reports/{uuid}/authenticate with the password unlocks it. Wrong passwords are rate limited per report. Right ones never are, so a shared page keeps loading for readers who have it.

POST /v1/projects/{id}/reports/{rid}/rotate-uuid mints a new public_uuid. If the link was shared, the new one works at once and every copy of the old one answers 404. If it was not shared, rotating does not share it. The response says which, as share_link.

Deleting a project does not delete its reports. They become team-wide incidents, and their share links keep working.

Deleting one

DELETE /v1/projects/{id}/reports/{rid} is for owners and admins, or any role with project_admin:write. A member gets 403. It removes the incident in one go, with its entries, its alert links, its timeline, its issue links and its status page phases, so the alerts it held can be attached to another incident. Its share link stops resolving. Its number is never handed out again: delete INC-7 and the next incident is still INC-8 or later. This is the only way to delete an incident, and it works only for one with a home project. A team-wide incident cannot be deleted, only declined.

Last updated 3 Oct 2026 Report a problem with this page