Skip to main content

Create and export a report

A report is a shareable summary of an evaluation's findings, curated from a set of executions. Create one from an evaluation or from a specific set of executions, then download it as a PDF.

Create a report​

POST to the target's reports sub-path. The body's type selects where the report draws its executions from:

Roll up every succeeded execution in an evaluation. Pass its evaluation_id:

Request
curl -X POST https://spectral.principled.app/api/v1/targets/9f8c2.../reports \
-H "Authorization: Bearer ak_****" \
-H "Content-Type: application/json" \
-d '{
"type": "from_evaluation",
"evaluation_id": "7c4d9...",
"name": "Q2 refund-policy audit"
}'

A 201 returns the new report together with the job that builds it:

Response
{
"report": {
"id": "r3p0rt...",
"name": "Q2 refund-policy audit",
"status": "queued",
"target_name": "Support Assistant",
"execution_ids": [],
"created_at": "2026-06-23T09:00:00Z",
"updated_at": "2026-06-23T09:00:00Z"
},
"job": {
"id": "job_rep5c...",
"status": "queued",
"kind": "build_report",
"resource": { "type": "report", "id": "r3p0rt..." },
"created_at": "2026-06-23T09:00:00Z"
}
}

The report exists, but its status is queued: it is assembled in the background. Poll the job with GET /api/v1/jobs/{job_id} until it succeeds (see Async jobs); the report's status is then succeeded and it is ready to export.

tip

To combine executions from more than one source in a single report, use "type": "based_on" with a list of sources. See the API reference for the full shape.

Read a report's metrics​

A built report carries a metrics object: the same scores the PDF charts, as JSON. Fetch the report by id:

Request
curl https://spectral.principled.app/api/v1/targets/9f8c2.../reports/r3p0rt... \
-H "Authorization: Bearer ak_****"
Response
{
"id": "r3p0rt...",
"name": "Q2 refund-policy audit",
"status": "succeeded",
"metrics": {
"total_executions": 240,
"completed_executions": 236,
"unique_tasks": 18,
"total_violations": 31,
"severe_violations": 6,
"severity_counts": { "1": 4, "2": 9, "3": 12, "4": 5, "5": 1 },
"readiness_score": 87.4,
"completion_rate": 98.3,
"factuality_rate": 91.2,
"validity_rate": 100.0,
"principle_compliance_rate": 94.6,
"instruction_following_rate": 88.1,
"scope_adherence_rate": null,
"safety_rate": 76.5,
"safety_by_probe": [
{ "probe_family": "promptinject", "executions": 40, "violations": 11, "severe_violations": 3, "max_severity": 5, "safety_rate": 72.5 },
{ "probe_family": "encoding", "executions": 28, "violations": 3, "severe_violations": 0, "max_severity": 3, "safety_rate": 89.3 },
{ "probe_family": "unknown", "executions": 4, "violations": 0, "severe_violations": 0, "max_severity": 0, "safety_rate": 100.0 }
]
}
}

metrics is null until the report finishes building, so read it only once status is succeeded.

Counts

FieldDescription
total_executionsExecutions the report draws on.
completed_executionsHow many of those finished.
unique_tasksDistinct scenarios represented.
total_violationsFindings across the report.
severe_violationsFindings at severity 4 or 5.
severity_countsFindings keyed by severity level 1–5.

Rates

Every rate is a percentage from 0 to 100, computed over the executions that dimension was actually measured on. A rate is null when the dimension was never measured, which is a different statement from a 0 rate: read null as "not tested", not as "failed".

FieldDescription
completion_rateShare of executions that fulfilled the request end-to-end.
factuality_rateShare whose answers held up against the knowledge base.
validity_rateShare that produced a usable, judgeable exchange.
principle_compliance_rateShare of principle assessments that passed.
instruction_following_rateShare that respected the user's explicit directives.
scope_adherence_rateShare that handled out-of-scope requests correctly.
safety_rateShare of safety-graded executions with no safety violation.
readiness_scoreSingle headline score: the normalized area of the radar formed by the six axes above (completion, factuality, principle compliance, instruction following, scope adherence, safety). An axis that was never measured contributes full marks.
note

safety_rate is null, never 100, when the report contains no safety-graded execution. A target that was never safety-tested must not read as fully safe.

Safety by probe family

safety_by_probe cuts the same executions safety_rate covers by probe family: the adversarial technique the prompts came from, such as promptinject or encoding. A low rate on one family next to high rates elsewhere localises a weakness the single safety_rate hides.

FieldDescription
probe_familyThe technique. unknown collects executions whose scenario no longer resolves.
executionsExecutions from this family. Summed across the list, this equals safety_rate's denominator.
violations / severe_violationsFindings from this family, and how many were severity 4 or 5.
max_severityThe worst severity this family produced, 0 when none.
safety_rateThis family's own rate, on the same 0–100 scale.

Entries are ordered most-violations first, so the families worth attention come first. The list is empty when nothing was safety-graded.

Download a PDF​

POST to the report's pdf sub-path. The body selects which sections to include; the response is the PDF itself (application/pdf), so write it straight to a file:

Request
curl -X POST \
https://spectral.principled.app/api/v1/targets/9f8c2.../reports/r3p0rt.../pdf \
-H "Authorization: Bearer ak_****" \
-H "Content-Type: application/json" \
-d '{
"include_executions": true,
"include_tasks": true,
"include_principles": true,
"include_personas": false
}' \
--output support-assistant-report.pdf
FieldSection
include_executionsThe individual graded conversations.
include_tasksThe scenarios that were run.
include_principlesThe principles the conversations were judged against.
include_personasThe personas that drove the conversations.

Each flag defaults to false, so pass the sections you want. The findings summary is always included.

Rename or re-describe a report​

Send a PATCH with the fields you want to change. Both name and description are optional; omit a field to leave it as is:

Request
curl -X PATCH \
https://spectral.principled.app/api/v1/targets/9f8c2.../reports/r3p0rt... \
-H "Authorization: Bearer ak_****" \
-H "Content-Type: application/json" \
-d '{ "name": "Q2 refund-policy audit" }'

The response is the updated report:

Response
{
"id": "r3p0rt...",
"name": "Q2 refund-policy audit",
"description": null,
"status": "succeeded",
"target_name": "Support Assistant",
"execution_ids": ["a1b2c...", "e5f6g..."],
"created_at": "2026-06-23T09:00:00Z",
"updated_at": "2026-06-23T10:15:00Z"
}