API Reference#
Warning
Project Neo is currently in Technical Preview. Features described in this section may change before general availability.
The HTTP API is the surface that the genai CLI wraps. Use it directly for browser-side clients and programmatic deployment workflows. For the command line client, see the CLI Reference page.
The API is small. Every route is scoped to a Squirro project, the platform project identified by the {project_id} in the path. That value appears in the address bar after /neo/ when you open a project in Project Neo. The administrative routes are addressed as /admin/projects/{project_id}/..., and the scheduled task routes as /projects/{project_id}/tasks/.... The paths below are shown relative to the GenAI service base. A deployed instance serves that base at /service/genai/v1, so prepend it to each route:
https://<squirro-cluster>/service/genai/v1/admin/projects/{project_id}/deploy
Every response that carries a body carries JSON, and the one route that returns none is the task delete, which answers 204.
The routes are reachable from outside the cluster, over the same host that serves the web interface, and every call is authenticated as described in the next section. Whether the service is exposed depends on the web server configuration of your deployment, so if a request does not reach the service at all, check with whoever administers the instance. For the GenAI service API as a whole, of which these routes are one part, see the GenAI service API page.
Deploy a Snapshot#
POST /admin/projects/{project_id}/deploy[?dry_run=true]
Content-Type: application/json
Publishes a new configuration snapshot for the project. With ?dry_run=true, the server runs every check it would run for a real deploy without writing the snapshot.
The request body carries the full configuration:
Field |
Type |
Required |
Notes |
|---|---|---|---|
|
list |
yes |
Every envelope-bearing resource, in any order: exactly one |
|
object |
no |
Flat key and value metadata recorded on the deploy receipt. Up to 64 entries, with bounded key and value lengths. |
The server runs every publish check over the submitted payload. On success, the payload replaces the current snapshot in a single all-or-nothing switch: either the whole configuration becomes live or nothing changes.
A successful response carries the deploy receipt:
{
"content_hash": "sha256:...",
"labels": { "git.hash": "abc1234" },
"timestamp": "2026-05-14T11:32:00+00:00",
"actor": "alice@example.com",
"dry_run": false
}
The content_hash is a hash over the validated snapshot. Two deploys with identical content produce identical hashes. On a dry run the snapshot is validated but not written, and the response marks itself as a dry run, with content_hash set to the placeholder (dry-run) because no snapshot was stored to hash.
Read Deploy Status#
GET /admin/projects/{project_id}/status
Returns the deploy receipt for the current snapshot without transferring the snapshot itself. Returns 404 when the project has no snapshot deployed.
{
"content_hash": "sha256:...",
"labels": { "git.hash": "abc1234" },
"timestamp": "2026-05-14T11:32:00+00:00",
"actor": "alice@example.com"
}
This is the deploy receipt without the dry_run field, which only a deploy or a reseed response carries. The timestamp is the time of the deploy that produced the current snapshot, not the time of your request, so an unchanged content_hash and timestamp across two calls mean nothing has been deployed in between.
Read Deploy History#
GET /admin/projects/{project_id}/log[?since=...&until=...&actor=...]
Returns the deploy history for the project, most recent first, as an entries list rather than a bare list. Each entry has the same shape as the status response.
Parameter |
Type |
Notes |
|---|---|---|
|
RFC 3339 timestamp |
Include only entries at or after this time. |
|
RFC 3339 timestamp |
Include only entries at or before this time. |
|
string |
Include only entries where the actor matches exactly. |
All parameters are optional and combinable.
{
"entries": [
{
"content_hash": "sha256:...",
"labels": { "git.hash": "abc1234" },
"timestamp": "2026-05-14T11:32:00+00:00",
"actor": "alice@example.com"
},
{
"content_hash": "sha256:...",
"labels": { "git.hash": "9f2e5d1" },
"timestamp": "2026-05-09T08:47:19+00:00",
"actor": "bob@example.com"
}
]
}
The history holds receipts alone, so it tells you what was deployed and by whom, not what the configuration contained.
Read the Current Snapshot#
GET /admin/projects/{project_id}/snapshot
Returns the current snapshot as a flat list of envelope-bearing resources, suitable for writing to disk. Backs genai export. Returns 404 when the project has no snapshot deployed.
The response has the same shape as the resources field of a deploy body, which is what lets an exported snapshot be redeployed unchanged. Agent instructions are carried inline, so no file is left behind.
{
"resources": [
{
"kind": "Project",
"schema": "v1",
"title": "My first project",
"prefers": { "inference.model": "claude-opus" }
},
{
"kind": "Model",
"schema": "v1",
"metadata": { "id": "claude-opus" },
"provider": "anthropic",
"model_name": "claude-opus-4-8",
"auth": { "type": "bearer", "secret": "anthropic_token" }
},
{
"kind": "Agent",
"schema": "v1",
"metadata": { "id": "assistant" },
"entrypoint": true,
"instructions": "You are a helpful assistant. Answer questions clearly and concisely."
}
]
}
The Project resource comes first, then the other resource kinds, then the agents. Every field of every resource is serialized, including the ones left at their default, so a real response is longer than the extract above.
Note
This route always returns the current snapshot. There is no parameter to retrieve a past snapshot by content hash. GET .../log returns only the receipt for a past deploy, not its resources. To restore a previous configuration, redeploy it from version control, as described in the Roll Back a Deployment section.
Read the Picker Payload#
GET /admin/projects/{project_id}/resources
Returns the picker payload: the description an interface fetches once at startup so it can display the settings a user is allowed to change, and offer only the values each setting accepts. It gives the settings that apply across the project, then, for each agent a user can select, only the settings where that agent differs.
Pins appear as a fixed value, constraints narrow the set of allowed values, and preferences appear as the default value. Per-request information about which source supplied which value does not appear on this surface, which describes only what a user may choose.
The payload has four top-level fields. project names the project, resources lists the catalogs a user picks from, baseline.params carries those project-wide settings, and assistants lists the selectable agents, each with the settings where it differs from baseline.
Note that resources means something different here from the resources list of a deploy body or a snapshot response. On this route it is the set of catalogs a user chooses from, such as the available models, and not the deployed resource files.
{
"project": { "title": "My first project", "summary": "" },
"resources": {
"models": [
{
"id": "claude-opus",
"title": "Claude Opus",
"summary": "",
"enable_reasoning": true,
"enable_image_input": true
}
],
"mcp_servers": [],
"knowledge_graphs": [],
"trait_dimensions": [
{
"id": "style",
"title": "Style",
"summary": "",
"traits": { "concise": { "title": "Concise", "summary": "" } }
}
]
},
"baseline": {
"params": {
"inference.model": {
"type": "string",
"title": "Model",
"description": "Model used for generating responses.",
"enum": ["claude-opus"],
"x-catalog": "models",
"default": "claude-opus"
},
"source.documents.enabled": {
"type": ["boolean", "string"],
"title": "Documents",
"description": "Use document retrieval for this conversation.",
"enum": [true, false, "auto"],
"default": "auto"
},
"trait.style": {
"type": ["string", "null"],
"title": "Style",
"enum": [null, "concise"]
}
}
},
"assistants": [
{
"id": "assistant",
"title": "Assistant",
"summary": "",
"opening": null,
"pin_to_conversation": false,
"overlay": {
"params": {
"trait.style": {
"type": ["string", "null"],
"title": "Style",
"enum": [null, "concise"],
"const": "concise"
}
}
}
}
]
}
Each entry under params is a JSON Schema fragment, so a form renderer consumes the payload as it stands. enum is the set of allowed values, narrowed by any constraint. default is the resolved preference. const is a fixed value, which is what a pin becomes: the extract above shows an agent that pins trait.style to concise, so it appears in that agent overlay and not in the baseline. x-catalog names the catalog under resources that the allowed values come from.
An overlay lists only the paths whose fragment differs from the baseline, and it carries the whole fragment for each. The form for one agent is therefore its overlay entry for a path where it has one, and the baseline entry otherwise.
A toggle whose backing resource kind has nothing published loses true from its enum and carries "x-ui-hint": "capability_unavailable", so an interface can explain why the switch cannot be turned on. This route reports no per-user authentication state for MCP servers, so every entry under mcp_servers reports authenticated: false.
Rebuild From the Bundle#
POST /admin/projects/{project_id}/reseed[?dry_run=true]
Content-Type: application/json
Rebuilds the bundled-workspace snapshot for the project and re-deploys it. The request body is optional and accepts two fields: force (boolean, default false) and labels (the same flat key and value metadata as a deploy, recorded on the resulting receipt).
By default the server refuses with a 409 response to overwrite a snapshot that was deployed by an operator. Set force to true to override that guard. A project with no current snapshot is seeded unconditionally.
With ?dry_run=true, the server rebuilds the bundled snapshot and runs every check without writing it. When the project does not have a valid bundled workspace, the server responds with 500 and an error body identifying the workspace and the failures.
This route is available only when the platform ships a bundled workspace for the project. For what a bundled workspace is and when to reseed, see the genai reseed section on the CLI reference page.
Configuration Resolution#
A resolve route reports how each setting resolves for a given agent, user, and request, and it backs genai explain. It is a development-time facility rather than part of the surface a Squirro instance serves: it ships with the development application of the configuration library, which serves it alongside the administrative routes, whereas a deployed instance serves the administrative and task routes only.
Point tooling that depends on resolution at a local run of that application, and treat what it reports as a debugging aid rather than something to build a product on. The four sources it reports on are described on the Overview page.
Scheduled Tasks#
These routes back the genai task commands. They manage scheduled tasks, which are not part of the deployed configuration. For what a task is and how approval rules work, see the Scheduled Tasks and Approvals page.
List and Create Tasks#
GET /projects/{project_id}/tasks[?limit=50&offset=0&sort=updated_at&order=desc]
POST /projects/{project_id}/tasks
Content-Type: application/json
GET returns the tasks of the calling user in the project, wrapped in the pagination fields shown in the list response below.
Parameter |
Type |
Notes |
|---|---|---|
|
integer |
Between 1 and 100. Defaults to 50. |
|
integer |
Zero or greater. Defaults to 0. |
|
string |
One of |
|
string |
|
POST creates a task and responds with 201 and the created task.
Field |
Type |
Required |
Notes |
|---|---|---|---|
|
string |
yes |
Between 1 and 100 characters. |
|
string |
yes |
What the agent is asked to do on each run. |
|
object |
yes |
When the task fires. See the trigger table below. |
|
boolean |
no |
The task-level switch. Defaults to |
|
boolean |
no |
Defaults to |
|
object or null |
no |
Approval rules for this task, as |
The trigger object carries a type that selects its shape. Today the only type is schedule, and its enabled field controls the schedule alone, independently of the task-level enabled.
{
"name": "Nightly document digest",
"instruction": "Summarize the documents added today and post the summary.",
"trigger": {
"type": "schedule",
"enabled": true,
"schedule": {
"time": "02:00",
"timezone": "Europe/Zurich",
"recurrence": "daily"
}
}
}
Field |
Type |
Required |
Notes |
|---|---|---|---|
|
string |
yes |
Local time of day, as |
|
string |
yes |
An IANA timezone name, such as |
|
string |
yes |
|
|
list of integers |
for |
Non-empty, with 0 for Sunday through 6 for Saturday. |
|
integer |
for |
Between 1 and 28 for |
|
integer |
for |
Between 1 and 12. |
A task carries the identity of the user who created it, and each run acts as that user. The number of tasks one user may hold in a project is capped, and a create beyond that cap returns 409.
A list response looks like this:
{
"tasks": [
{
"id": "3f9a1c7e-5b2d-4e18-9c0a-71d4e2f6b8aa",
"name": "Nightly document digest",
"instruction": "Summarize the documents added today and post the summary.",
"enabled": true,
"trigger": {
"type": "schedule",
"enabled": true,
"schedule": {
"time": "02:00",
"timezone": "Europe/Zurich",
"recurrence": "daily",
"days_of_week": null,
"day_of_month": null,
"month_of_year": null
}
},
"email_notifications_enabled": false,
"hitl": { "rules": [ { "pattern": "send_email", "disposition": "defer" } ] },
"next_execution_at": "2026-05-15T00:00:00Z",
"last_execution_at": "2026-05-14T00:00:00Z",
"last_execution_status": "succeeded",
"conversation_count": 12,
"created_at": "2026-05-01T09:14:03Z",
"updated_at": "2026-05-14T00:00:12Z",
"created_by": "alice@example.com"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
The task object is the same on every route that returns one task, so POST, GET .../tasks/{task_id}, and PATCH all answer with the object shown inside tasks above. The id is assigned by the platform and is the {task_id} of every other route. Timestamps are UTC. next_execution_at and last_execution_at are null until the schedule has a next slot and the task has run at least once, and the schedule fields that the recurrence does not use are present and null.
Read, Update, and Delete a Task#
GET /projects/{project_id}/tasks/{task_id}
PATCH /projects/{project_id}/tasks/{task_id}
DELETE /projects/{project_id}/tasks/{task_id}
GET returns one task, in the shape shown under List and Create Tasks: the fields you supplied, plus the ones the platform sets for you.
PATCH changes only the fields present in the body. The trigger is the exception: it is replaced as a whole, so send the complete object to change any part of the schedule. Because the task-level enabled is a field of its own, pause or resume a task by sending enabled alone.
DELETE responds with 204. The run history and the conversations produced by past runs are kept.
Run a Task and Read Its History#
POST /projects/{project_id}/tasks/{task_id}/runs
GET /projects/{project_id}/tasks/{task_id}/runs[?limit=50&offset=0]
POST /projects/{project_id}/tasks/{task_id}/runs/{run_id}/retry
Content-Type: application/json
POST .../runs fires the task immediately and responds with 201. A manual run fires whatever enabled and trigger.enabled are set to, which lets you test a task before its schedule starts it.
GET .../runs returns the run history, most recent first, wrapped in the same pagination fields as a task list:
{
"runs": [
{
"id": "418",
"task_id": "3f9a1c7e-5b2d-4e18-9c0a-71d4e2f6b8aa",
"status": "succeeded",
"trigger_source": "scheduled",
"conversation_id": "b70c25d9-8f43-4a61-8e2c-0d3f9a1b6c47",
"conversation_title": "Nightly document digest",
"error_message": null,
"scheduled_for": "2026-05-14T00:00:00Z",
"started_at": "2026-05-14T00:00:04Z",
"completed_at": "2026-05-14T00:00:12Z"
}
],
"total": 14,
"limit": 50,
"offset": 0
}
A run id is a number in string form, unlike the task id, which is a UUID. The status is one of running, awaiting_approval, succeeded, incomplete, failed, or timed_out, where awaiting_approval is a run parked on a defer rule and incomplete is a run that stopped without finishing while keeping the output it had produced. The trigger_source is scheduled or manual. A manual run fills no schedule slot, so its scheduled_for is null, and a run that has not finished carries a null completed_at. The same object is returned by POST .../runs and POST .../retry.
POST .../retry re-runs a past run as a new manual run and responds with 201. Only a run that failed or ended without finishing can be retried, and any other run returns 409. The optional body takes a single field, conversation_id, to continue an existing conversation instead of starting one.
Error Contract#
An error raised by the service carries a detail field. A request rejected before it reaches the service, which is the authentication case, does not.
Status |
Meaning |
Detail shape |
|---|---|---|
|
Malformed payload, or a business-rule violation caught before validation. On a task list, an unsupported |
A string. |
|
No credentials were supplied, or the access token is invalid or has expired. Exchange the refresh token for a new access token and retry. |
Produced before the request reaches the service, so read the status code rather than the body. |
|
The caller does not hold the Administrator role on the project, or, on a task route, cannot read the project. |
Supplied by the host application. |
|
A status, snapshot, or resources request against a project with no snapshot. On a task route, a task or run that does not exist or does not belong to the caller. |
A string. |
|
A reseed against an operator-deployed snapshot without |
A string. |
|
Publish checks failed on deploy, or request validation failed on another route. |
On a deploy, an object of the form |
|
A reseed whose bundled workspace could not be built. |
An object identifying the workspace and the failures. |
A 422 on deploy is the canonical publish-rejected shape. A client should read detail.failures as the list of failures rather than treating the response as a single-line error. For how to read the list and fix each kind of failure, and for the common causes behind the other status codes, see the Troubleshooting page. When the failure is an unresolvable credential handle, register the credential as described in the Registering a Handle section.