Necto Platform API
v0.1.0

Necto platform API

v0.1.0

The HTTP surface serving three views: the developer creates a governed agent, the security owner reviews the policy diff, the operator works the agent card. No workflow or marketplace endpoint. The contract changes before the implementation does, never after.

The platform API is a private in-cluster service with no public hostname; a public endpoint will be published only once external reach is decided.

Base URL
The platform API is a private in-cluster service with no public hostname; a public endpoint will be published only once external reach is decided.
Authentication
bearer (JWT)
Audience-bound assertion
OpenAPI
Download the spec
The single source of this page

Authentication

Every endpoint requires a bearer assertion in the Authorization header.

Short-lived, audience-bound identity assertion, verified by the API and never trusted from the browser. A human caller presents the console's assertion; a machine caller presents a Kubernetes projected service-account token (issuer = the cluster's service-account issuer, audience = necto-api, lifetime at most 600 seconds). The customer's identity provider proves who someone is; Necto alone decides what they may approve. Under federation the console assertion carries upstream_identity_path: ordered issuer, subject, verification_basis entries, origin first. Malformed paths are refused with 401. A machine caller is authorised by its committed client-principal record alone: an operation the record does not list is refused with 403, and a machine caller never approves a release.

Agents

Developer view: create a governed agent

GET

/agents

Not yet served

List governed agents

Not yet available.

Responses

503
429
200

Agents visible to the caller

application/json → array of AgentSummary

POST

/agents/profile/compile

Not yet served

Compile a profile and return its artifacts

Not yet available.

Request

Body: application/octet-stream

Responses

503
429
200

Compiled artifacts and their hashes

application/json → CompiledProfile

422

Unknown field, missing required field, or denied default

application/json → Problem

POST

/agents/{agentId}/changes

Not yet served

Open a pull request changing this agent's profile

Not yet available.

Parameters

agentId *
path · string

Request

Body: application/json

profile *
string

necto.yaml content

Responses

503
429
201

Pull request opened

application/json → object

pullRequestUrl *
string (uri)
POST

/tasks/compile

Compile a `kind: Task` and check that it narrows, never widens

A task compiles to a restriction set intersected with the target agent's already-approved compiled artifacts. Because a task widens no compiled artifact, a narrowed result needs no approval. If the intersection is not exactly what the task requested, compiling is refused with the offending field, never a coerced narrowing.

Request

Body: application/octet-stream

Responses

503
429
200

The narrowing result

application/json → TaskCompileResult

404

No such agent

application/json → Problem

422

The task did not parse, or it widened the agent's authority; either way the offending field is named. A widening refusal also carries requested and granted.

application/json → Problem

403
GET

/tasks

List scheduled tasks

Every kind: Task apply that carried a schedule. A one-shot task, one with no schedule, runs once and is never listed here, the same way nectoctl apply -f necto.yaml never lists a run.

A machine caller reads only the scheduled tasks of the one agent its operation is scoped to, whether or not it names that agent; naming another agent is refused. A human caller reads every scheduled task.

Parameters

agentId
query · string

The agent whose scheduled tasks to list. Optional for a machine caller, whose own operation already scopes the answer; naming another agent is refused. A human caller reads every agent's tasks.

Responses

503
429
200

Scheduled tasks visible to the caller

application/json → object

tasks *
array of TaskSummary
403

Releases

Security owner view: policy diff and approval

GET

/releases

Not yet served

List releases awaiting review

Not yet available.

Responses

503
429
200

Releases

application/json → array of ReleaseSummary

POST

/releases

Compile a profile and open a release

Compiles, opens a release awaiting approval, and stops; it never changes a running agent. via: pull_request is the default and the required carrier in a customer production environment; via: direct records the profile in Necto's release store instead of a git commit, and is refused with 422 there. imageDigest is the prebuilt artifact the release binds -- an E2B template build id (a UUID), built outside this process -- and the governed run starts its sandbox from it. Optional only for a release that binds no artifact; a governed run refuses such a release rather than starting from a stage's default template. A template name, alias or tag is refused: it is movable, and a rebuild would change what runs with no new release. An OCI image digest is refused until an OCI path exists; only E2B template ids are accepted today.

A release for an agent the platform does not hold registers it from this profile: its diff is computed against a profile that grants nothing, so every source, tool, egress destination and authority change shows as added. A release for an agent the platform holds is diffed against the profile that agent's last release registered.

Request

Body: application/json

agentId *
string
profile *
string

necto.yaml content

via
pull_request, direct
imageDigest
string

The prebuilt artifact the release binds: an E2B template build id (a UUID), never a template name, alias or tag, and never an OCI image digest until an OCI path exists.

Responses

503
429
201

Release opened

application/json → ReleaseSummary

422

via: direct refused because the context environment is production, the profile failed to compile, agentId differs from the profile's name, or agentId or profile is a count where an identity is required: a count is never an identity, and no release is created from one

application/json → Problem

403
GET

/releases/{releaseId}/diff

The compiled policy diff

Computed on compiled artifacts, not on file text. A formatting-only change returns noPolicyChange: true with an empty changes array. The release's own approval window is approvalExpiresAt, and the recorded standing approval is rendered beside the diff with its own expiresAt when one is recorded and not revoked, so a client can warn before either lapses and renew it while the window admits a second approval.

Parameters

releaseId *
path · string

Responses

503
429
200

Policy diff, with the release's approval window, the recorded standing approval when one is live, and the standing-approval proposal the approver would approve beside it. The proposal is the deployment's own resolution, never a request value; absent when nothing resolves, or replaced by the recorded revocation when the release's grant has been revoked.

application/json → PolicyDiff

403
GET

/releases/{releaseId}/tests

Required-test results for this release

Parameters

releaseId *
path · string

Responses

503
429
200

One entry per required test, with its stored artifact

application/json → array of RequiredTestResult

403
POST

/releases/{releaseId}/approval

Approve or reject the compiled diff hash

The approver approves a hash, not a description. The request carries diffHash; if it no longer matches the release's current hash the approval is refused with 409. Refused with 409 when a required test failed, or when a citation no longer resolves.

Parameters

releaseId *
path · string

Request

Body: application/json

decision *
approve, reject
diffHash *
string
reasonCode
not-needed, wrong-scope, test-failure, citation-gone, unsafe-change, other

A rejection's structured reason code, from the closed set. A rejection requires one; an approval carries neither a code nor a reason text. A code outside the set is refused 422 with field: reasonCode, and nothing is recorded.

reason
string

Optional free text stored beside a rejection's code, shown beside the decision and escaped on render. It is never parsed for ids or commands, and a text over 200 characters is refused 422 with field: reason.

standingApproval
object

The standing Slack grant the approver gives with this approval: the channel id the diff read exposed, the caps and the expiry the approver confirms. channelId is an echo of the deployment's resolution -- the API refuses any other value -- and only an approve decision may carry it.

Responses

503
429
200

Decision recorded in Necto's evidence store

application/json → ApprovalRecord

403

The caller authenticated and Necto grants them no approval authority. No identity-provider claim changes this. Distinct from 401, and never a read-only 200. A machine client never approves a release at all: its approval request is refused 403 even when its client-principal record lists this route.

application/json → Problem

409

Hash no longer current (approval void), required test failed, an unresolvable citation, a widening release approved by the customer without the Necto approval the two-party rule needs, or a standing approval that does not match the deployment's resolution: the echoed channel id is not the resolved one, the release's agent has no scheduled task, or the deliver alias resolves to no channel. title names which, because a caller cannot act on them the same way. A widening release needs both parties' distinct approvals on one hash: a customer's approval without the Necto approval is refused and nothing is recorded, while a Necto approval recorded before the customer's own is one of two and the release is not approved and not runnable until the customer's own distinct approval binds the same hash. A non-widening release is approvable by the customer's security owner alone.

application/json → Problem

410

Unapproved release expired after seven days. What would have been approved is no longer approvable.

application/json → Problem

422

The body is malformed: decision or diffHash is missing, diffHash is a count where a release identity is required, a rejection carries no reasonCode, an unknown reasonCode, a reason over 200 characters, or a code or text on an approval; standingApproval is malformed, names caps other than the standing approval's, or an expiry that is not a timezone-aware instant inside the standing approval's window, or rides a reject; or the release's own compiled profile does not compile write_external. field names which, and nothing is recorded.

application/json → Problem

POST

/releases/{releaseId}/standing-approval/revoke

Revoke the release's standing Slack approval

Any human holding approval authority for this agent may revoke, and the act is recorded. Revoking is never harder than granting, so the grant's original approver is not required. Every standing grant the release's current diff hash bounds is revoked, never only the latest one; the revocation is checked at the next scheduled mint and never interrupts a send already in flight (the named lag). The grants are not edited: the revocation stands beside them. A machine principal may never approve or revoke.

Parameters

releaseId *
path · string

Responses

503
429
200

Revocation recorded in Necto's evidence store

application/json → StandingApprovalRevocation

403

The caller authenticated and Necto grants them no approval authority, or the caller is a machine principal. A machine principal may never approve or revoke; no identity-provider claim changes either.

application/json → Problem

404

No release under this id, or no standing approval is recorded for the release's current diff hash.

application/json → Problem

Applications

Application customer plane: register the primary customer object and read it back

GET

/applications

List the deployment's applications

Every application this deployment holds, in creation order. The tenant is the deployment's own; no request field selects it, and another customer's applications are never listed.

Responses

503
429
200

Applications visible to the caller

application/json → object

applications *
array of Application
403
POST

/applications

Register an application

The application is the primary customer object. One application keeps one stable identity across dev, stage and prod; this route registers that identity, and every environment, plan, deployment and record below keys on it. The body names the application and the source it is created from — one of the four accepted sources. The field set is closed: there is no field for code, a credential or persistent data, and an unexpected field is refused rather than ignored. The tenant is the deployment's own and never a request field; the creator is the assertion-resolved subject, never a body field.

Request

Body: application/json

name *
string

The application's display name

Responses

503
429
201

Application registered

application/json → Application

403
422

A field is missing or malformed, the source kind is not one of the four accepted sources, the OCI reference is not addressed by digest, or the body carries a field outside the closed set. field names it.

application/json → Problem

GET

/applications/{applicationId}

Read one application

One application by its stable identity, or 404 when this deployment holds none — including an application of another customer, which is not readable through this deployment's store.

Parameters

applicationId *
path · string

Responses

503
429
200

The application

application/json → Application

404

No such application for this deployment

application/json → Problem

403
GET

/applications/{applicationId}/services

Read the managed state one application environment owns

One logical database and role, bucket and queue per application environment, each with its own state and its own credential reference. Identifiers only — no field carries a credential value and no field carries payload content. The environment is the deployment's own selection; an environment the application holds no state for answers an empty list. Another customer's application is not one this deployment holds, and its read is a 404.

Parameters

applicationId *
path · string
environment *
query · dev, stage, prod

Responses

503
429
200

The managed state this application environment owns

application/json → ApplicationServices

404

No such application for this deployment

application/json → Problem

422

The environment query parameter is missing, or names an environment outside dev, stage and prod. field names it.

application/json → Problem

403
POST

/applications/{applicationId}/services

Set up the managed state one application environment owns

The platform derives every name and reference; the request names only the environment. The logical database and role, bucket and queue names, and their credential references are the application-environment derivation's, never body fields. Setup is per service: while a provider does not answer, that service's state is recorded failed, never reported as success, and no credential is issued for it. A body field outside the closed set is refused rather than ignored. The tenant is the deployment's own; no request field selects it.

Parameters

applicationId *
path · string

Request

Body: application/json

Responses

503
429
201

The state recorded for this application environment

application/json → ApplicationServices

404

No such application for this deployment

application/json → Problem

422

A field is missing or malformed, the environment is not one of the three, or the body carries a field outside the closed set. field names it.

application/json → Problem

403
POST

/applications/{applicationId}/secrets

Record a secret's reference

The value is held by the secret manager; the platform records a reference. The body carries the secret's name and value exactly once; the answer carries the reference the platform derived from the tenant, the application, the environment and the name, and never the value. No record the platform writes holds a value. A blank name or value is refused rather than defaulted. The tenant is the deployment's own; no request field selects it.

Parameters

applicationId *
path · string

Request

Body: application/json

Responses

503
429
201

The secret's name and recorded reference

application/json → SecretReference

404

No such application for this deployment

application/json → Problem

422

A field is missing or malformed, the environment is not one of the three, or the body carries a field outside the closed set. field names it.

application/json → Problem

403
GET

/applications/{applicationId}/plans

Read the application's plan versions

Every plan version this application holds, oldest first. A version is immutable and identified by a content hash over the tenant key, the plan and its parent, so reading one back is reading what was recorded — never a later correction. provenance states, per plan field, whether the winning value came from detection or an explicit value. The tenant is the deployment's own and no request field selects it; another customer's application is not one this deployment holds, and its read is a 404.

Parameters

applicationId *
path · string

Responses

503
429
200

The application's plan versions, oldest first

application/json → object

applicationId *
string
versions *
array of ApplicationPlanVersion
404

No such application for this deployment

application/json → Problem

403
POST

/applications/{applicationId}/plans

Detect a plan and record its version

Detection produces the visible, editable plan; the plan grants no authority. The application's registered source selects the construction path: a repository or template is detected through the adapter's closed input contract, a Dockerfile through the minimal read, and a prebuilt image skips detection. A key outside a closed contract, a language outside the fixture-tested set, and a detection that does not resolve to one plan are all refused rather than guessed; support is claimed per tested fixture only. saved is the saved project setting level; a field no level supplies is a refusal, never a default. The resolved plan is minted as an immutable version and answered with its per-field provenance. A detection resolving to the plan the application already holds answers with that version and records nothing new. The tenant is the deployment's own and no request field selects it.

Parameters

applicationId *
path · string

Request

Body: application/json

Responses

503
429
201

Plan version recorded

application/json → ApplicationPlanVersion

200

The plan the application already holds; identical content is the same version, so nothing new is recorded

application/json → ApplicationPlanVersion

404

No such application for this deployment

application/json → Problem

403
422

A body field outside the closed set, a key outside the adapter's or the saved setting's closed contract, a language outside the fixture-tested set, a detection that does not resolve to one plan, or a Dockerfile the minimal read cannot resolve. field names it.

application/json → Problem

POST

/applications/{applicationId}/plans/corrections

Correct the current plan version

A correction is a new immutable version, never a mutation of the one it corrects. The body's values is the closed necto.app.yaml schema — the correctable plan fields. A key outside it, toolchain and source included, is refused as unknown, and customer_id is refused like any other unknown key because the deployment supplies the tenant key, never a request. Every field the correction does not set keeps the value and the provenance it had; every field it sets carries the entered level. The corrected plan must differ from the current version: identical content is the same version, refused rather than recorded as a second one. The corrected version's parent is the current version, so a correction never forks the lineage. An application with no recorded plan has nothing to correct and answers 404.

Parameters

applicationId *
path · string

Request

Body: application/json

Responses

503
429
201

Corrected plan version recorded

application/json → ApplicationPlanVersion

404

No such application for this deployment, or the application holds no plan version to correct

application/json → Problem

403
422

values is missing or not the closed necto.app.yaml schema, a key outside it is refused as unknown, or the correction changes nothing — identical content is the same version, not a new one. field names it.

application/json → Problem

GET

/applications/{applicationId}/guardrails

Read the application's graph and default guardrails

The read-only graph and the default guardrails, resolved for one application's accepted plan version. Every field carries the source it came from and the accepted plan version it was read at. The plan is topology only: a graph field is never compiled into a guardrail, so a tool, source, destination or credential appears only where the governed profile declares it. The placement is computed from the accepted plan's requested tier and the governed profile, never customer-forced. A refusal — a conflicting placement or a governed profile that does not compile — is visible in refusals with the policy decision's own reason and safe metadata, never a generic application error. The tenant is the deployment's own; another customer's application is a 404.

Parameters

applicationId *
path · string

Responses

503
429
200

The graph, the default guardrails and any visible refusal

application/json → ApplicationGuardrails

404

No such application for this deployment, or the application holds no accepted plan version.

application/json → Problem

403
POST

/applications/{applicationId}/deployments

Deploy the accepted plan to dev

A dev deployment gets an automatic record; no approval. The accepted plan version is named by its content hash and reconciled into the application environment's resources: standard components become restricted Kubernetes resources and governed components runtime start requests. Placement is computed by the platform — the namespace from the tenant, application and environment, and the tier from the accepted plan and governed profile — and neither is read from a body field. The governed profile hash and the compiled-artifact hashes on the record are the platform's own derived values. A deployment whose governed profile or compiled artifacts differ from the environment head record's is refused without an approval; the deployment-approval path is not built yet. Nothing reaches a cluster here; the live adapters are a later ticket.

Parameters

applicationId *
path · string

Request

Body: application/json

Responses

503

The namespace derivation or the tier classification seam is not wired. Refused fail-closed; never defaulted.

The state store is busy when another writer holds its write lock past the store's own wait: the request is answered 503 with a Retry-After header before the route acts. The store releases its own lock before refusing, so later operations proceed.

application/json → Problem

429
201

Deployed; the environment's automatic deployment record

application/json → DeploymentRecord

404

No such application for this deployment

application/json → Problem

403
409

The deployment changes the governed profile or the compiled artifacts the environment head record carries, and no approval is bound to the change. The deployment-approval path is not built yet, so the deploy refuses rather than landing unapproved.

application/json → Problem

422

A body field outside the closed set, a missing or malformed image digest, an accepted plan hash the application does not hold, a governed component with no registered governed profile, or a placement the platform refuses. field names it.

application/json → Problem

Promotions

Application customer plane: promote one application through dev, stage and prod

POST

/applications/{applicationId}/promotions

Open a promotion request to the next environment

One hop at a time, dev to stage to prod; skipping a stage is a refusal, never a warning. The request carries only an image digest, an accepted plan version and identifiers — there is no field that could carry a credential or persistent data, and none is accepted. The governed profile and compiled-artifact hashes the approvers sign are the platform's own derivation from the application's registered governed profile, never request fields. The tenant is the deployment's own; no request field selects it. The target environment's namespace is derived by the platform from (customer, application, environment) and never read from this body; the runtime tier is computed from the requested tier and the governed profile, never posted. Both seams refuse fail-closed with 503 while unwired.

Parameters

applicationId *
path · string

Request

Body: application/json

fromEnvironment *
dev, stage, prod
toEnvironment *
dev, stage, prod
imageDigest *
string

sha256:<hex>; an OCI image addressed by digest, never a tag

acceptedPlanHash *
string
sourceRevision *
string
detectorVersion *
string
builderVersion *
string
requestedTier *
standard, governed

the accepted plan's requested tier; the platform computes placement

tests
array of object

Test-name and artifact-URL identifier pairs — the record links tests, never their content

Responses

429
403
201

Promotion request opened, awaiting two distinct approvers

application/json → PromotionRequest

422

A field is missing or malformed, the environments name a hop that is not the next one, the image is not addressed by digest, or the requested tier is unknown. field names it.

application/json → Problem

503

The namespace derivation or the tier classification seam is not wired. Refused fail-closed; never defaulted.

The state store is busy when another writer holds its write lock past the store's own wait: the request is answered 503 with a Retry-After header before the route acts. The store releases its own lock before refusing, so later operations proceed.

application/json → Problem

POST

/promotions/{promotionId}/approval

Approve or reject a promotion, bound to the combined hash

The approver approves a hash, not a description. Two distinct, assertion-resolved approvers are required before a promotion may execute. The requester cannot approve their own promotion; a subject has one decision per promotion; an approval naming anything but the promotion's current combined hash is refused. The approval records the target environment's head deployment record at decision time, and expires with the deployment's approval window.

Parameters

promotionId *
path · string

Request

Body: application/json

decision *
approve, reject
combinedHash *
string

Responses

503
429
200

Decision recorded

application/json → PromotionApproval

403

The caller authenticated and Necto grants them no approval authority. No identity-provider claim changes this. A machine principal never approves a promotion either: its request is refused 403 even when its client-principal record lists this route.

application/json → Problem

409

A named refusal: self-approval refused, approver decided twice, or promotion hash mismatch — title names which, because a caller cannot act on all of them the same way.

application/json → Problem

POST

/promotions/{promotionId}/execute

Execute an approved promotion

Executes only when two distinct assertion-resolved approvals bind the promotion's combined hash, neither has expired, the target environment's head deployment record is still the one the approvals recorded, and a private-e2b target's placement gate is proven. The platform recomputes the governed profile hash and the compiled-artifact hashes from current state: when they differ from what the approvals signed, the approval is void and nothing is recorded as deployed. The new deployment record lands through a compare-and-swap on the target head: two concurrent executes succeed exactly once. No override parameter exists.

Parameters

promotionId *
path · string

Responses

503
429
201

Promoted; the deployment record for the target environment

application/json → DeploymentRecord

403
409

A named refusal: promotion rejected, insufficient approvers, approval expired, approval void (the governed profile or compiled artifacts changed after approval), target changed (head moved, or the compare-and-swap lost), or gate unproven for a private-e2b target. title names which.

application/json → Problem

Collaboration

Application customer plane: the tenant's private collaboration journal and version history

GET

/applications/{applicationId}/journal

The application's collaboration mutation journal, newest first

Every mutation records the actor and the resulting version. The append-only journal is read one application at a time, newest first, tie-broken by insertion order the way the durable backend's rowid does. Every field is an identifier — subject, object id, version — never a comment body, prompt or payload. The deployment's own tenant key is carried on the record and is never selected by the request. A caller whose assertion verified and for whom Necto holds no authority record is refused 403; a machine principal is refused by its own client-principal record.

Parameters

applicationId *
path · string

Responses

503
429
200

The application's mutation journal, newest first

application/json → object

applicationId *
string
mutations *
array of MutationRecord
403
404

No application is registered in this deployment under this id. A cross-tenant application is not registered here either: the registry is tenant-keyed and this read never crosses it.

application/json → Problem

GET

/applications/{applicationId}/history

The application's version history, derived at read time

Read-only by shape. The history is derived at query time from the stores that already own the rows — the accepted plan versions, the deployment records, the release records and the run registry — and writes nothing. Every entry carries the anchor kind, the identifier its store knows it by, the time and the actor where the store has them. A plan version has no clock and no actor on the artifact; a run record has no actor; the entry reports the absence rather than inventing one. A plan version's parent is carried by hash. The plan store has no application key, so the tenant's plan versions are part of every application read; a version is not attributed to one application by a key that does not exist. The deployment store's only list-shaped read is the record in force per environment, so the history lists the current head and not every superseded record. The tenant is the deployment's own and is never taken from the request.

Parameters

applicationId *
path · string

Responses

503
429
200

The application's version history, newest first

application/json → object

applicationId *
string
entries *
array of HistoryEntry
403
404

No application is registered in this deployment under this id. A cross-tenant application is not registered here either: the registry is tenant-keyed and this read never crosses it.

application/json → Problem

POST

/applications/{applicationId}/collaborators

Grant a collaboration role on an application

The first administrative surface. The caller resolves in Necto's authority store — the operator is the first administrator — and the granted subject is named by its issuer and subject, never by an email address. The grant is per application; the store is bound to the deployment's own tenant key, so no request, claim or header selects another. Every grant appends a role_grant record to the mutation journal before the store moves. An approving grant alone does not approve: approval still reads the authority record.

Parameters

applicationId *
path · string

Request

Body: application/json

issuer *
string

The issuer half of the granted subject's key

subject *
string

The stable subject identifier; an email address is refused

role *
viewing, editing, deploying, approving, stopping

Responses

503
429
201

Grant recorded in Necto's journal and held by the store

application/json → CollaboratorGrant

403
404

No application is registered in this deployment under this id. A cross-tenant application is not registered here either: the registry is tenant-keyed and this route never crosses it.

application/json → Problem

409

The subject already holds a grant on this application. A role change is a revocation and a new grant, never an edit.

application/json → Problem

422

The body is malformed or carries a field the closed set does not name; issuer, subject or role is missing or blank; the role is not one of the five; or the subject looks like an email address. field names which, and nothing is recorded. Never a 500.

application/json → Problem

DELETE

/applications/{applicationId}/collaborators/{subject}

Revoke a collaboration role on an application

Revoking is never harder than granting. The caller resolves in Necto's authority store; the grant is removed, never edited, and the revocation appends a role_grant record to the mutation journal. A revocation with no grant in force is refused 409: every recorded revocation is attached to the version it removed.

Parameters

applicationId *
path · string
subject *
path · string

The stable subject identifier of the grant to revoke

issuer *
query · string

The issuer half of the grant's key; a subject is only unique within its issuer

Responses

503
429
200

Revocation recorded in Necto's journal

application/json → CollaboratorGrant

403
404

No application is registered in this deployment under this id. A cross-tenant application is not registered here either.

application/json → Problem

409

The subject holds no grant on this application to revoke. A no-op revocation is refused, never filed.

application/json → Problem

422

The issuer query parameter is missing or blank, or the path's subject looks like an email address. field names which, and nothing is recorded. Never a 500.

application/json → Problem

POST

/applications/{applicationId}/clone

Clone an application's plan version into a new application

A clone is a new application carrying the source plan version's topology only. The clone's new plan version is minted field by field from the source plan's ten topology fields, with the source version hash as its parent; the source version, application and repository are unchanged. No approval, deployment, release, run, role grant, credential, secret or managed-state resource is carried: none has a plan field or a parameter to travel through, and each environment of the clone resolves its own secrets. One plan_version cloned record is appended to the mutation journal, naming the actor, the source version hash and the clone's version hash; a record the journal refuses leaves no clone. The source version is read through this deployment's own tenant-keyed plan registry, so another customer's version is not found — refused 404, never cloned. The caller needs an editing collaboration grant on the source application; without one the clone is refused 403. The field set is closed: a field outside it is refused rather than ignored, and a blank or malformed field is refused 422, never a 500.

Parameters

applicationId *
path · string

Request

Body: application/json

sourceVersion *
string

The source plan version hash (sha256:<hex>); the clone's new version is parented to it

newApplicationId *
string

The clone's new stable application id; the clone's plan version is keyed by it and no request field selects the tenant

Responses

503
429
201

The clone's new plan version recorded; the source application and its version are unchanged

application/json → ApplicationClone

403

The caller resolves in Necto's authority store but holds no editing grant on the source application, or the source version is another customer's. Nothing is cloned either way.

application/json → Problem

404

No application is registered in this deployment under this id, or the application holds no plan version with this hash. A cross-tenant source version is not one this deployment's registry holds either.

application/json → Problem

409

The clone's new application id is already registered. A clone mints a new identity, never overwrites one.

application/json → Problem

422

The body is malformed or not valid UTF-8, or it carries a field the closed set does not name; sourceVersion or newApplicationId is missing, blank or not a string. field names it, and nothing is cloned. Never a 500.

application/json → Problem

GET

/applications/{applicationId}/comments

List one application's comments, newest first

One row per comment, and a redacted comment carries no body. Each item is the newest row of one comment: the identifier, the application, the anchor, the actor and the time of that newest row, whether it is a redaction, and how many revisions the store holds. A live comment carries the newest body; a redacted comment carries no body at all, because a redaction hides the body rather than overwriting it. The optional anchorKind and anchorId filters narrow the list to one anchor, and both are needed. The read needs the caller's viewing grant on this application: no grant is 403, and the store is bound to the deployment's tenant key, so another customer's comments are never listed. An unknown anchor kind is refused 422 rather than answered with a whole application's comments.

Parameters

applicationId *
path · string
anchorKind
query · plan_version, deployment, run, policy_version

The anchor kind to filter by; anchorId is needed beside it.

anchorId
query · string

The anchor's own identifier to filter by.

Responses

503
429
200

One application's comments, newest first

application/json → object

applicationId *
string
comments *
array of Comment
403
404

No application is registered in this deployment under this id. A cross-tenant application is not registered here either.

application/json → Problem

422

An anchor filter is malformed: anchorKind is not one of the four anchor kinds, anchorKind is given without anchorId, or anchorId is blank or given without anchorKind. field names which, and nothing is read. Never a 500.

application/json → Problem

POST

/applications/{applicationId}/comments

Record a comment on an application

The body is user content and enters no trail. The caller needs the editing grant on this application: a viewing grant alone is refused 403, and no grant is refused 403 before anything is recorded. The actor is the caller's resolved subject key and the time is the platform clock, never request fields. The comment store appends the row and writes the one comment journal record; the route writes no second record. The response carries the recorded comment's identifiers and never the body: the body is read back through the list route, and no comment body enters the journal or the evidence timeline. A comment id the store already holds is refused 409: an edit is a revision, never a second creation.

Parameters

applicationId *
path · string

Request

Body: application/json

commentId *
string

The comment's own identifier, stable across its revisions.

body *
string

The comment content. A blank body is refused 422, and a non-UTF-8 body is refused 422 and never reaches the store.

Responses

503
429
201

Recorded in the comment store and the mutation journal; the comment's identifiers and revision count. The body is not echoed.

application/json → Comment

403
404

No application is registered in this deployment under this id. A cross-tenant application is not registered here either.

application/json → Problem

409

The comment id is already recorded in this store. An edit is a revision of the same comment id, never a second creation.

application/json → Problem

422

The body is malformed or carries a field the closed set does not name; commentId, anchor or body is missing, blank or not a string; the anchor kind is not one of the four; or the body is not UTF-8. field names which, and nothing is recorded. Never a 500.

application/json → Problem

POST

/applications/{applicationId}/comments/{commentId}/redactions

Redact a comment

A redaction appends a revision; it never mutates or deletes one. The caller needs the editing grant on this application: a viewing grant is refused 403, and no grant is refused 403 before the comment is read. The actor is the caller's resolved subject key and the time is the platform clock, never request fields. The store appends one redaction row carrying the version of the revision it hides; the hidden revision stays in the store and the journal keeps both versions, never overwritten and never deleted. The response carries the new row's identifiers — including the redacted revision — and never a body: a redacted body enters neither the journal nor the evidence timeline. A comment this deployment does not hold is 404, and a comment already redacted is 409: a redaction is final, and a later revision cannot put the body back. A comment of another application in this tenant is not this application's: the editing grant is per application, so the redaction never reaches it.

Parameters

applicationId *
path · string
commentId *
path · string

The comment's own identifier, stable across its revisions.

Responses

503
429
201

Recorded in the comment store and the mutation journal: the redaction's identifiers, the redacted revision id and the revision count. No body is echoed.

application/json → Comment

403
404

No application is registered in this deployment under this id, or the application holds no comment under this comment id. A cross-tenant comment is not held here either, and a comment of another application in this tenant is not this application's either: the editing grant is per application.

application/json → Problem

409

The comment is already redacted. A redaction is final and a second one is refused, never filed.

application/json → Problem

422

The store refused the redaction rather than filing it: the platform clock is not after the comment's newest revision, so the append would break the append-only order. field names the comment, and nothing is recorded. Never a 500.

application/json → Problem

Operations

Operator view: agent card, evidence, stop, revoke

GET

/applications/{applicationId}/budget

The application's run budget for its current window

The per-application run budget: the cap on runs in one fixed window, the window's own count and the runs remaining. The claim that enforces it happens at run admission, in the same step as the run's own registration, so a start past the cap refuses before any sandbox starts and records no run. Runs are the counted dimension; a spend dimension joins when the metered instrument records it. No budget recorded is a 404: the read answers the record and never a default cap. The tenant is the deployment's own; no request field selects it.

Parameters

applicationId *
path · string

Responses

429
403
404

No run budget is recorded for this application.

application/json → Problem

200

The current window's budget

application/json → ApplicationBudget

GET

/agents/{agentId}/card

The agent catalogue card

Denied requests are returned alongside successful runs, not behind a filter.

Parameters

agentId *
path · string

Responses

503
429
200

Agent card

application/json → AgentCard

403
GET

/agents/{agentId}/behaviour/baseline

What is usual for this agent, by release regime

The baseline a run is compared against: the documents, destinations, tool order, records-per-run and run-seconds the agent's release regimes have actually shown. A baseline is keyed to one agent, one compiled profile and one release, so a release opens a new regime instead of being reported as drift on release day; a prior regime's baseline is kept for comparison and never scores the next one. transitions names where one regime's baseline ended and the next began.

The four dimensions are facts, never a score. state is ready when the regime has enough observed history to compare against, and insufficient_volume when it does not: the state scores nothing, and the comparison carries no verdict rather than a low one. observedRuns is the number of the regime's finished runs the baseline summarises. Identifiers only: document ids, destination names and tool names, never content, passages, prompts or credentials.

Parameters

agentId *
path · string

Responses

503
429
200

The agent's regime-keyed baseline

application/json → BehaviourBaseline

403
404

No such agent is registered.

application/json → Problem

POST

/agents/{agentId}/runs

Start a governed run

Returns identifiers only: no document text, no raw prompts, no credential values. The run is asynchronous; watch /runs/{runId}/timeline?watch=1 for the ordered TimelineEntry stream.

A registered machine client may start the run from a schedule tick by sending a trigger. The run then takes its input from the scheduled task the trigger names, records the tick on the run's timeline, and carries the trigger on the card's run summary. A caller that is a person, a trigger that names no task of this agent, an instant that is not a tick of the task's schedule, or a tick outside its own period is refused, and nothing starts. A tick is outside its own period when it is more than one minute after the current instant, or more than one period of the task's own schedule plus a short transport margin before it: the tick is stale once the next tick exists. A tick refused as stale is recorded as a skipped tick on the agent's schedule stream, while a tick refused before its period is due is recorded on that stream under the distinct kind schedule_refused, without a tick key, so the refusal does not pre-decide it. A repeated trigger key starts no second sandbox: the response carries the run the key already names.

Parameters

agentId *
path · string

Request

Body: application/json

input
string

The caller's message for this run. Required for a run with no trigger; a scheduled run takes its input from the scheduled task the trigger names.

Responses

429
200

The trigger key already named a run, so no second sandbox starts. The response carries the existing run id and replayed: true.

application/json → object

runId *
string
replayed *
boolean
202

Run accepted. The sandbox is stopped as soon as the entrypoint returns, and the stop is recorded on the run's timeline: sandbox_killed when it stopped, or sandbox_stop_failed when it did not, so a sandbox left running is never reported as stopped. The substrate's inactivity timeout stays only as the backstop for a crashed coordinator path, and the run's own exit result is unchanged by a failed stop. The moment the entrypoint returns, the run's timeline gains run_finished with the exit code and the stdout/stderr byte counts, never the output.

application/json → object

runId *
string
startedAt
string (date-time)
artifactDigest
string

The prebuilt artifact the sandbox ran from: an E2B template build id (a UUID). Present only for a governed run; the in-process reference workload starts no sandbox and names no artifact.

sandboxId
string

The substrate's own sandbox identifier. Present only for a governed run.

exitCode
integer

The artifact entrypoint's exit result. Present only for a governed run; 0 ran, and the entrypoint's own non-zero code is recorded unchanged.

409

The agent is contained, a request arrives while the agent already has a run that has not finished, the tick is outside its own period, the tick is already recorded as skipped, or the previous run's sandbox did not stop: it stays registered as live and this run starts no second sandbox, so no agent ever has two live sandboxes. One agent has one live run: a second start, triggered or not, starts no second sandbox, and a triggered one is recorded as a skipped tick with reason overlap. An agent with no approved release starts nothing: a release whose approval is rejected, void or expired, and a widening release whose second distinct approval has not arrived, are not approved releases, so the run is refused before a sandbox exists. Stopping an agent prevents new runs as well as killing the running one; a stop that let the next run start would be a pause. Returning to service is a release, not this call succeeding on a retry. A stop that lands while a run is starting kills the sandbox that started, and the kill is recorded on that run's timeline, so no sandbox is left running after a stop. A tick that is refused is recorded as a skipped tick on the agent's schedule stream, with reason contained, overlap or refused: a run the release gate refuses is an authority refusal, and so is a tick outside its own period. A tick refused by a gate is never also a run, and a repeat of its key answers the skip rather than a run. A tick outside its own period is audited on that stream; only one whose period is over is recorded with its tick key. A tick already recorded as skipped is never caught up: the refusal names the reason it was recorded with.

application/json → Problem

403

The caller may not start a triggered run: a trigger comes from a registered machine client, and a person's trigger is refused.

application/json → Problem

422

The request names neither a trigger nor an input, or the trigger is malformed: its key does not match the task id and the scheduled instant, or it names no task of this agent, or the instant is not a tick of that task's schedule. A malformed trigger names no tick the scheduler could have observed, so nothing is recorded as a skipped tick. A triggered run the governed path then refuses without authority — a profile that does not compile — is recorded as a skipped tick with reason failed.

The application's run budget is exhausted: the claim past the cap refuses before the run is registered and starts no sandbox, and a triggered tick is recorded as a skipped tick with reason refused.

application/json → Problem

503

The substrate is unavailable: no run entrypoint is configured, the sandbox did not start, or it did not execute the run. A triggered tick refused before its run is recorded — no run entrypoint — is recorded as a skipped tick on the agent's schedule stream, with reason failed: the substrate failing is not the authority refusing. A triggered tick whose run record exists and whose sandbox then fails is a failed run: its key names that run, the failure is recorded on the run's own timeline (sandbox_start_failed when the sandbox did not start, sandbox_exec_failed and the kill when it did not execute the run), no skipped tick is written beside it, and a repeat of its key answers the recorded run.

The state store is busy when another writer holds its write lock past the store's own wait: the run route answers 503 with a Retry-After header and starts no sandbox. The store releases its own lock before refusing, so later operations proceed.

application/json → Problem

GET

/agents/{agentId}/schedule-events

The agent's schedule stream, newest first

Every tick this agent's scheduler recorded, newest first: the accepted ticks beside the ticks that did not run. An accepted tick is the run's own schedule_fired entry; a stale tick is the keyed schedule_skipped with reason refused; a tick refused before it could have run is the distinct schedule_refused with reason future and no key; the remaining skips carry contained, overlap, failed or missed. Each entry carries its tick key where one exists, its task id and its scheduled instant beside the instant the platform recorded it. A human caller with authority for the agent reads the stream; a machine client and a workload-originated call are refused, because the scheduler reports ticks and never reads them back. An unknown agent is refused like the card. limit defaults to 50 and a value outside 1 to 200 is refused by name rather than clamped.

Parameters

agentId *
path · string
limit
query · integer

How many of the newest entries to return. Default 50; a value outside 1 to 200 is refused rather than clamped.

Responses

503
429
200

The agent's schedule stream, newest first: the accepted ticks and the ticks that did not run, each with its timestamps; the ticks that did not run carry their reason.

application/json → object

agentId *
string
entries *
array of TimelineEntry
403

The caller authenticated and Necto holds no authority record for the subject, or the caller is a machine client or a workload-originated call. The schedule stream is read by a human caller with authority for the agent; a machine client reports ticks and never reads them back.

application/json → Problem

404

No such agent

application/json → Problem

422

The limit is not an integer, or it is outside 1 to 200. The refusal names the field rather than clamping the value, so a caller never reads a shorter answer as the one it asked for.

application/json → Problem

POST

/agents/{agentId}/schedule-events

Record a tick the scheduler observed as missed

Machine-only. A scheduler reports a tick it did not fire because it was past its grace window. The API recomputes the trigger key from the task id and the scheduled instant and records a skipped tick with reason missed on the agent's schedule stream. A key that already names a run or a skipped tick is a no-op: the scheduler's boot look-back reports every past-grace tick it can see, and the API is what de-duplicates. A caller that is a person, or a request that does not name a missed tick of this agent, is refused.

Parameters

agentId *
path · string

Request

Body: application/json

Responses

503
429
200

The skipped tick is recorded, or the key already named a run or a skipped tick and nothing new is written.

application/json → object

recorded
boolean
403

The caller may not report a missed tick: schedule events come from a registered machine client, and a person's report is refused.

application/json → Problem

422

The report is malformed: its key does not match the task id and the scheduled instant, or it names no task of this agent, or the instant is not a tick of that task's schedule.

application/json → Problem

POST

/agents/{agentId}/actions

Mint and execute one standing-approved send

The scheduled-run send surface. A scheduled run mints one fresh, ordinary, single-use ActionEnvelope under the standing approval it names and executes it through the existing single-use consumption path: no second consumption model, no reusable envelope, and no adapter change. The grant approves one channel and one message schema; the envelope names the grant in approvalRef and the grant's approver in caller, so the adapter's existing per-call revalidation is the check the send passes. Every refusal the standing-approval rules define is this route's own refusal, never a 500: an unknown grant is 404; a revoked grant, a channel outside the grant, a URL host the profile's allowlist does not admit and a tool the compiled allowlist does not permit are 403; a release change that voided the grant is 409; an expired grant is 410; a payload outside the approved schema, a field over its length cap, a title or price carrying URL-like text, or a url that is not exactly one absolute https URL is 422; and a mint over the per-run or per-day cap is 429. A workload-originated call is authorised by its own run. The console's workload relay verifies a DPoP-bound workload token and mints the assertion with the verified workload claims beside the sandbox instance id as sub: agent_id, run_id, tool and audience. A call carrying those claims is refused on every route but this one, and on this route it is refused unless that run exists, is live, was started by Necto for this agent, runs in that instance, the tool is one the run delivered, the instance's workload tokens have not been revoked by a stop, this request's runId and tool are the assertion's, and the forwarded audience is the verified tool's own per-tool audience necto:<agentId>:<tool>. The revocation is read from the deployment's own revocation list by the instance the assertion names; a deployment with no such list to read, or a list that cannot be read, refuses the call rather than assuming the instance is not revoked. Only the run's own workload call may spend the standing approval. A session assertion is refused here with 403, whatever authority it holds: a human approves each send through the ordinary approval surface. The per-run cap is counted on the run the relay verified, never on a value in this request, so a request naming another run is refused and cannot reset it.

Parameters

agentId *
path · string

Request

Body: application/json

grantId *
string

The standing approval the send is minted under

runId *
string

The scheduled run this mint is for. The per-run cap is counted on the run the relay verified, never on this value; a request naming another run is refused and cannot reset it.

channel *
string

The channel the run intends to post to. The grant is scoped to exactly one channel id; anything else is refused.

tool *
string

The tool the run invokes. The relay's verified tool claim must name exactly this tool, and the tool is revalidated against the agent's compiled action allowlist at execution time.

payload *
object

The one message schema the grant approves, exactly: an extra or missing field is refused, never dropped or supplied. Every field is bounded: a longer value is refused before any pattern runs. The URL must be exactly one absolute https URL -- no whitespace, no second URL and no userinfo -- whose host the profile's compiled egress allowlist admits. The title and price are plain text: URL-like text in either -- a scheme, www., a domain whose final label the IANA root zone delegates, or an obfuscated spelling of one -- is refused, so a URL cannot ride a field the host check does not read.

Responses

429

Two limits, told apart by the body. The surface's rate limit refused the request before the route ran: rate limited with Retry-After. The standing approval is at its per-run or per-day cap: the title names the cap.

application/json → Problem

201

The send was executed once, by identifier

application/json → object

envelopeId *
string
idempotencyToken *
string

Derived from the envelope hash, never minted at execution; the ledger consumes it exactly once.

channel *
string
messageTs *
string

Slack's own identifier for the message it created. The message text is never returned or recorded.

404

No such agent, or no standing approval is recorded under the named grant id

application/json → Problem

409

The agent is contained, the grant is void because the release changed after approval, or the agent has no approved release to bind the grant to

application/json → Problem

410

The standing approval expired; the next mint is refused

application/json → Problem

403

The deployment has not opted in to the standing approval, the caller is not the run's own workload call, the request's run is not the verified run, the forwarded audience is not the verified tool's, the instance's workload tokens were revoked by a stop, no workload-token revocation list is composed to read, that list could not be read, the grant was revoked, the channel is outside its scope, the URL host is not one the profile's allowlist admits, or the compiled allowlist does not permit the tool

application/json → Problem

422

A required field is missing, the payload is not the approved message schema, a field is over its length cap, the title or price carries URL-like text, the url is not exactly one absolute https URL, or the profile did not compile

application/json → Problem

502

Slack refused the send or could not be reached. The detail carries Slack's own error code, never the message text.

application/json → Problem

503

The deployment composes no Slack transport, or its Slack credential is not configured.

The state store is busy when another writer holds its write lock past the store's own wait: a write that precedes the action it records -- the credential-novelty entry, the identity assertion or the authority journal -- fails closed, so the request is answered 503 with a Retry-After header before the route acts. The store releases its own lock before refusing, so later operations proceed. The per-request audit record is written after the outcome exists: its write is retried a bounded number of times, and if the store stays busy the response stands and one structured warning names the gap.

application/json → Problem

POST

/agents/{agentId}/egress-refusals

Record one destination a governed run's egress refused

The relayed refusal report. A newly onboarded agent starts with zero egress permissions, so every destination it tries is refused; the sandbox reports the refused destination's identifiers and the platform records them for the customer's review. Identifiers only: the host, the port and the protocol, the run's governed-component identity and the run, the platform's own instant and the bounded attempt counter; no payload, content or capture field exists. A workload-originated call is authorised by its own run. The console's workload relay verifies a DPoP-bound workload token and mints the assertion with the verified workload claims beside the sandbox instance id as sub. A call carrying those claims is refused on every route but the relayed routes, and on this route it is refused unless that run exists, is live, was started by Necto for the path's agent, runs in that instance, the instance's workload tokens have not been revoked by a stop, and the tool is one the run delivered. The revocation is read from the deployment's own revocation list by the instance the assertion names; a deployment with no such list to read, or a list that cannot be read, refuses the call rather than assuming the instance is not revoked. The body's field set is closed: host, port and protocol only, and an unexpected key is refused. The tenant key, the run's governed-component identity and the run come from the deployment and the verified claims, never from this body: the body carries no runId, so a caller cannot file a refusal under another run. A repeat observation coalesces into its row's counter up to the platform's cap, and a new destination past the run's cap is not recorded: the answer is 202 with recorded: false and reason: flood-cap, and the run is not failed. The cap value is set by the change that lands the row, never by this document.

Parameters

agentId *
path · string

Request

Body: application/json

host *
string

The named host the run's egress refused. A wildcard, a CIDR, a scheme, a port or a path is refused; the host is normalized before it is stored.

port *
integer

The TCP port the run tried

protocol *
tcp, tls

The connection's closed protocol set

Responses

201

The refused destination was recorded; a repeat observation answers the same way with its counter coalesced

application/json → ObservedDestination

202

The run already recorded the platform's cap of distinct destinations; this new one is not recorded and the run is not failed

application/json → object

recorded *
False
reason *
flood-cap
403
422

A required field is missing, the host is not a named host, the port is outside 1-65535, the protocol is not tcp or tls, or the body carries a field the report does not declare

application/json → Problem

429
503
GET

/runs/{runId}/timeline

One run, end to end

Identifiers and decisions only: no document text, no raw prompts. Dropped candidates are included with their reason; a timeline showing only what was retrieved hides the control that was exercised. watch=1 upgrades the response to a Server-Sent Events stream, one TimelineEntry per event; omit it for the current snapshot. A workload-originated read is authorised by its own run: the console's workload relay mints the assertion with the verified workload claims beside the sandbox instance id as sub, and a call carrying those claims is refused on every route but this one and the actions route. The read is refused unless that run exists, is live, was started by Necto for the assertion's agent, runs in that instance, the instance's workload tokens have not been revoked by a stop, and the tool is one the run delivered. The revocation is read from the deployment's own revocation list by the instance the assertion names; a deployment with no such list to read, or a list that cannot be read, refuses the read rather than assuming the instance is not revoked. A session assertion is decided exactly as before. Every entry carries its own evidence class: semantic for a kind the four behavioural dimensions read, infrastructure for every other kind. An entry presented as semantic agent evidence is checked by require_semantic before it is presented, and a refusal is the named 409 whose detail names the kind.

Parameters

runId *
path · string
watch
query · boolean

Stream new entries as Server-Sent Events instead of returning a snapshot

Responses

503
429
200

Immutable timeline entries, or an SSE stream when watch=1

application/json → array of TimelineEntry

text/event-stream → string

403
409

An entry presented as semantic agent evidence that the partition does not read as semantic: the refusal names the kind in detail, because a caller has to know what was handed over. A word and the kind, never a number and never a semantic claim.

application/json → Problem

GET

/runs/{runId}/chat

One run's chat stream, as AG-UI events

The small optional chat surface, behind a Necto-owned interface: AG-UI's event names on Server-Sent Events, so the protocol adapter may be replaced without changing the application or the evidence models. Every event is derived from the run registry and the run's own timeline: run state is the registry's own rows, and tool activity is the run's own credential-injection entries. Identifiers and decisions only: no message text, no tool arguments and no raw chain-of-thought is emitted or stored, because the platform stores none. The caller is authorised by the same record every run read uses: a caller Necto holds no authority record for is 403, and a run Necto never registered is 404 rather than an empty stream.

Parameters

runId *
path · string

Responses

503
429
200

One Server-Sent Events data: frame per event, each frame a JSON-encoded event whose type is an event name this interface emits.

text/event-stream → string

403
404

No such run was started by the platform.

application/json → Problem

GET

/runs/{runId}/behaviour

One run's behaviour against its regime's baseline

The same four dimensions for one run — documents read, destinations contacted, tools and their order, records opened and seconds elapsed — plus the comparison against the baseline of the regime that run belongs to, as it stood before the run started. Only that regime's runs that finished before this run started count, so a later or unfinished run never widens the baseline an earlier run is compared against. A run is never compared with another regime's baseline or with itself, and the response names the regime it was compared against.

Refused attempts are observations, never absences: an attempt the platform refused is returned with the rule that refused it. state is ready when the regime has enough observed history to compare against, and insufficient_volume when it does not: the state scores nothing, and the comparison carries no verdict rather than a low one. Identifiers only: document ids, destination names and tool names, never content, passages, prompts or credentials.

Parameters

runId *
path · string

Responses

503
429
200

The run's observed behaviour and its comparison

application/json → BehaviourObservation

403
404

No such run was started by the platform.

application/json → Problem

GET

/workload/revocation

Whether the calling workload instance's tokens are revoked

The console workload relay's own read, before it forwards a relayed request. The instance is the one the verified console workload assertion names as its subject; the tenant is the deployment's own and is never taken from a claim or a request value. The read takes no path or query parameter, performs no action and reads no run: it answers for a stopped instance too, so a revoked presentation is refused however fresh its proof is. Answers {"revoked": false} while the instance's tokens are live, and {"revoked": true} once a stop has revoked them. A caller that is not a workload assertion is refused: a human session and a machine principal are 403. A deployment whose revocation list is absent or unreadable is answered 503, so the relay forwards nothing rather than a presentation nobody checked.

Responses

200

Whether the instance's workload tokens are revoked

application/json → object

revoked *
boolean

True once a stop revoked this instance's outstanding workload tokens. The read answers this for a stopped instance as well.

403

The caller is not a workload assertion: a human session or a machine principal.

application/json → Problem

429
503

The deployment holds no readable workload-token revocation list, so no instance can be answered about.

application/json → Problem

POST

/agents/{agentId}/rollback

Not yet served

Release a previous version of this agent

Not yet available.

Parameters

agentId *
path · string

Request

Body: application/json

toVersion *
string

A prior currentVersion from this agent's card

Responses

503
429
201

Release opened, awaiting approval

application/json → ReleaseSummary

GET

/agents/{agentId}/authority/check

Whether this agent may use a tool, without invoking it

Answers nectoctl auth can-i. Evaluates the agent's compiled action allowlist for the calling identity; never invokes the tool and never mutates anything.

Parameters

agentId *
path · string
tool *
query · string

Responses

503
429
200

The authority level, or a refusal reason

application/json → AuthorityCheck

403
POST

/agents/{agentId}/stop

Stop the agent immediately

Not a release. No pull request, no approval, no build in this path. Safe to call on an already-stopped agent; still recorded.

Parameters

agentId *
path · string

Responses

503
429
202

Stop accepted and recorded. observedSeconds is how long this stop took, measured, not a promised ceiling. No bound is stated until one has been measured for the selected runtime and connector.

application/json → object

observedSeconds
number
workloadStopped
string
recordedAt
string (date-time)
403

The caller holds no operator authority for this agent

application/json → Problem

POST

/agents/{agentId}/revoke

Revoke the agent's credentials upstream

Invalidates upstream, verified against the upstream rather than against Vault's own view of its lease. Distinct from stop: nothing revokes a Vault lease when a workload dies.

Parameters

agentId *
path · string

Responses

503
429
202

Revoke accepted and recorded. Each entry in revocations states whether the upstream supports revocation at all and whether an upstream call made afterwards actually failed. A connector whose credential is not Necto's to invalidate reports supported: false rather than a success nobody can verify.

application/json → object

recordedAt
string (date-time)
revocations
array of object
403

The caller holds no operator authority for this agent

application/json → Problem

POST

/agents/{agentId}/export

Export a review bundle for a period

The export is itself an action, recorded in the timeline. The bundle carries identifiers and decisions, never content. Assembled in the request, from what the evidence store holds, so it returns with the identifier. complete is false when the period predates the retention cutoff: older entries may be purged.

Parameters

agentId *
path · string

Request

Body: application/json

from *
string (date-time)
to *
string (date-time)

Responses

503
429
200

The review bundle for the period

application/json → object

exportId *
string
bundle *
ReviewBundle
422

from or to is missing, is not an ISO-8601 instant, carries no offset, or the period ends before it starts

application/json → Problem

403
GET

/whoami

The caller's own identity and approval authority

Derived from the audience-bound assertion, never from the browser. Answers nectoctl auth whoami, and is distinct from /agents/{agentId}/authority/check, which answers for an agent rather than for the caller.

Responses

503
429
200

Caller identity

application/json → WhoAmI

403

The assertion verified, and Necto holds no authority record for this subject. Distinct from 401: the caller is who they say they are, and that is not by itself authority. Deny by default, never a read-only view of someone else's.

application/json → Problem

GET

/federation/providers

This deployment's federation registry records

Console service identity. The console server reads the committed federation registry live over its existing east-west path and keeps no copy of its own: the upstream issuers, the named endpoint hosts and the allowed verification bases this deployment's records hold, so a registry record and a console provider config cannot disagree silently. The read is the console server's own call, never a signed-in person's: an assertion that is not the console's service identity (iss = the deployment's console issuer, sub = necto-console) is refused. Tenant- and deployment-scoped: the records are the deployment's customer id's, read from the deployment and never from a request, claim or header. Identifiers and display fields only, never a secret: no client credential, domain or endpoint path is in the record or the answer. limit defaults to 200 and a value outside 1 to 500 is refused by name rather than clamped.

Parameters

limit
query · integer

How many records to return. Default 200; a value outside 1 to 500 is refused rather than clamped.

Responses

503

This deployment has no federation registry composed, so the route refuses rather than answering an empty list. A missing or malformed committed source refuses the boot through the loader instead.

application/json → Problem

429
200

This deployment's federation registry records, by issuer

application/json → array of FederationProvider

403

The assertion verified and the caller is not the console server's own service identity: a signed-in person, a machine client and a workload-originated call are refused. The registry read is the console's, not a user's.

application/json → Problem

422

limit is not an integer, or it is outside 1 to 500. The refusal names the field rather than clamping the value.

application/json → Problem

Schemas

Every object the API returns or accepts, with its fixed field set.

Problem

title *
string
status *
integer
detail
string
field
string

The offending profile field, for a 422

requested
string

What the caller asked for, on a kind: Task widening refusal from POST /tasks/compile: the single source, tool or authority value named by field.

granted
string

What the target already grants, alongside requested: the profile's own authority for a widened authority, or its comma-joined data/tools list for a source or tool the profile does not list.

AgentSummary

agentId
string
name
string
owner
string
purpose
string
environment
string
authority
read_only, draft_only, write_reversible, write_external
currentVersion
string

CompiledProfile

The compiled artifacts, each content-addressed, plus the containment plan added on acceptance.

profileHash
string
runtimeConfig
object
retrievalPermissions
object
credentialScopes
object
actionAllowlist
object
requiredTestMatrix
object
evidenceHeader
object
containmentPlan
object

Five questions for this agent: who may trigger containment, what triggers it, what changes immediately, what evidence remains, who can restore service. Declaring a plan is not evidence that containment works.

artifactHashes
object

ReleaseSummary

releaseId
string
agentId
string
diffHash
string
state
awaiting_approval, approved, rejected, void, expired
expiresAt
string (date-time)

Unapproved releases expire after seven days

via
pull_request, direct

How the profile text was recorded. direct is refused when the calling context's environment is production.

imageDigest
string

The prebuilt artifact the release binds. Present only for a release that binds one; absent reads as absent rather than as a blank a reviewer might take for a value.

PolicyDiff

diffHash *
string
noPolicyChange *
boolean

True for a formatting-only change

approvalExpiresAt *
string (date-time)

The release's own approval window: the instant the release stops being approvable, computed by the platform from the recorded release, never by the caller. Present on every diff read, after the window passed included.

changes *
array of object
standingApproval
StandingApprovalProposal
standingApprovalGrant
StandingApprovalGrant
standingApprovalRevoked
StandingApprovalRevoked

StandingApprovalGrant

The standing approval the release has recorded, beside the diff: the grant the next scheduled run mints under when one is spendable, otherwise the release's latest recorded grant, whose expiry has passed. Present when a grant is recorded and not revoked; absent when the release has no grant, and replaced by standingApprovalRevoked when the recorded grant was revoked. A grant past its own expiry is still rendered, because the expiry is the fact a client warns on and the approval route's own refusal is the enforcement.

grantId *
string

The grant the next scheduled run mints under when one is spendable, otherwise the release's latest recorded grant.

channelId *
string

The one channel the grant is scoped to

approver *
string

The human whose approval the grant records

grantedAt *
string (date-time)

The instant the grant was recorded

expiresAt *
string (date-time)

The instant the grant stops being usable. A second approval before the release's own window passes records a fresh grant; after it passes, renewal is a new release.

StandingApprovalProposal

What the approver would approve beside the diff: the release's agent's scheduled task deliver alias resolved to one Slack channel id by the deployment's composition, never a request value. Absent when the deployment registers no channel for the alias, the agent has no scheduled task, or the release's profile does not compile write_external.

channelId *
string

The one channel id the deployment resolved for the deliver alias. The approver echoes it back; the API refuses a value that is not this one.

messageSchema *
array of string

The one message schema the grant approves

perRunCap *
integer

The per-run cap, never a request value

perDayCap *
integer

The per-day cap, never a request value

maxExpiryDays *
integer

The expiry ceiling; a shorter expiry is the approver's choice

StandingApprovalRevoked

The recorded revocation of the release's standing approval, beside the diff. Present instead of standingApproval when the release's recorded grant has been revoked, so a client that does not know this field shows no approval rather than one that still looks live.

grantId *
string
revokedBy *
string

The human whose act ended the grant

revokedAt *
string (date-time)

StandingApprovalRevocation

The recorded revocation: one entry per grant the release's current diff hash bounds, each naming the human whose act it was. The grants are not edited and not deleted: these records stand beside them, and the next mint is refused from revokedAt on.

releaseId *
string
grants *
array of object

RequiredTestResult

name
retrieval_scope, denied_access, expired_credential, blocked_egress, output_format
passed
boolean
artifactUrl
string

The stored output. A pass without an artifact is a claim.

blocksRelease
boolean

Always true on failure

ApprovalRecord

releaseId
string
diffHash
string
decision
approve, reject
approver
string
party
customer, necto

The side of the two-approver rule the resolved authority record named when the decision was made. A widening release needs one customer and one necto approval by two distinct subjects; a non-widening release is approvable by the customer's own security owner alone.

decidedAt
string (date-time)
requiresTwoParties
boolean

Whether the release widened egress or tool authority against the release the platform ran when the decision was made. true marks a decision that needs one customer and one necto approval by two distinct subjects; an absent field is a one-party decision. Forward-only: a previous build refuses a record that states true rather than reading one party as two.

fallback
boolean

Whether the decision is the Necto-operator fallback: the operator approves only while no customer security owner resolves for the deployment. The record names the fallback explicitly and never claims the customer's approval; its party stays necto. Forward-only: a previous build refuses a record that states true rather than reading the operator as the customer's approval.

reasonCode
not-needed, wrong-scope, test-failure, citation-gone, unsafe-change, other

A rejection's structured reason code, from the closed set. An approval carries neither a code nor a reason text. The text beside it is stored and never parsed for ids or commands. Forward-only: a previous build refuses a record that states one rather than reading a rejection as an approval.

reason
string

The rejection's free text, stored verbatim beside its code and escaped on render. It is never parsed for ids or commands. Forward-only: a previous build refuses a record that states one rather than reading an unreviewed word as the reason code.

CollaboratorGrant

One recorded collaboration grant: the application, the granted subject's issuer and subject, the role, the grant's content-hash version, the actor whose act it was and the time. Every field is an identifier — never a display name, an address or a claim. customerId is the deployment's own tenant key, recorded with the grant and never selected by the request.

customerId *
string
applicationId *
string
issuer *
string
subject *
string
role *
viewing, editing, deploying, approving, stopping
version *
string

The content-hash identity of the grant state the act produced or removed

actor *
string
recordedAt *
string (date-time)

ApplicationClone

One clone act's result, as identifiers only: the clone's new stable application id, its new plan version hash and the source version hash the clone is parented to. Never a plan, a secret or any copied content.

applicationId *
string
versionHash *
string

sha256:<hex> of the clone's new plan version

parent *
string

The source version hash the clone is parented to

CommentAnchor

What a comment is attached to, by identifier: the anchor kind and the anchor's own id. kind is a closed set — an unknown kind is refused rather than filed. The id is an identifier, never the object it names.

kind *
plan_version, deployment, run, policy_version
id *
string

Comment

One comment's newest row, as identifiers and a count. redacted is true when the newest row is a redaction; revisionCount is how many rows the store holds — a count, never a prior body. body is the newest revision's content and is present only for a live comment: a redacted comment carries no body. redacts is the version of the revision a redaction hides and is present only when redacted is true; the hidden revision stays in the store, never mutated or deleted. The actor is a subject identifier, never a display name, and at is when the newest row was recorded.

commentId *
string
application *
string
actor *
string
at *
string (date-time)
redacted *
boolean
revisionCount *
integer
body
string

The newest revision's content; absent for a redacted comment, because a redacted body is never returned.

redacts
string

The version of the revision this row hides; absent unless redacted is true, because a live comment redacts nothing.

ApplicationBudget

applicationId *
string
runLimit *
integer

The cap on runs admitted in one window.

windowSeconds *
integer

The fixed window's length; a window is aligned to the Unix epoch, so the same instant maps to the same window everywhere.

windowStart *
string (date-time)
windowEnd *
string (date-time)
used *
integer

The runs claimed in this window.

remaining *
integer

The runs this window still admits, derived from the cap and the window's own count rather than carried beside them.

PromotionRequest

One promotion hop, as metadata only. There is no field for a credential or persistent data, structurally: promotion carries an image digest and an accepted plan version, never either.

promotionId
string
application
string
actor
string

The requester's assertion-resolved subject

fromEnvironment
dev, stage, prod
toEnvironment
dev, stage, prod
imageDigest
string
acceptedPlanHash
string
sourceRevision
string
detectorVersion
string
builderVersion
string
profileHash
string

The platform-derived content hash of the application's registered governed profile, or the explicit value none when none governs it. Never a request field.

governedRuntime
string

server-derived from the application's governed profile, never a request field; the private-e2b placement gate reads it

artifactHashes
array of object

The platform's own compiled-artifact hashes for the registered governed profile in the target environment, or empty when none governs it. Never a request field.

requestedTier
standard, governed
placementTier
standard, governed

The platform-computed placement

namespaceName
string

The platform-derived namespace for the target environment; supplied by the derivation seam, never by a caller.

tests
array of object
requestedAt
string (date-time)
combinedHash
string

What the two approvers bind: the combined deployment hash over the application, target environment, image digest, accepted plan and governed hashes.

PromotionApproval

promotionId
string
combinedHash
string
decision
approve, reject
approver
string
targetHead
['string', 'null']

The target environment's head deployment record id at decision time — what the approval vouched for alongside the hash.

decidedAt
string (date-time)

ApplicationSource

Where the application's code comes from — one of the four accepted sources. An identifier, never content: a repository location, a template identity, a Dockerfile location or a prebuilt OCI image addressed by immutable digest. No field here carries code, a credential or persistent data.

kind *
repository, template, dockerfile, oci_digest
reference *
string

The source's identifier: a repository location, a template id, a Dockerfile location, or a sha256:<hex> image digest — the immutable identity a prebuilt image is addressed by, never a tag.

Application

One application: the primary customer object, one stable identity across dev, stage and prod. Metadata only — the record names its source, never code or a credential. Every record carries the deployment's tenant key; no request field selects it.

applicationId *
string
name *
string
createdBy *
string

The assertion-resolved subject that registered it

createdAt *
string (date-time)

ManagedService

One managed service's own state in one application environment: the logical resource name and the credential reference the platform derived. Identifiers only — no field carries a credential value and no field carries payload content. state is the platform's own word: provisioned, or failed when the provider did not answer — never reported as success.

environment *
dev, stage, prod
service *
postgresql, object_storage, durable_queue
state *
provisioned, failed
resourceName *
string
credentialRef *
string
recordedAt
string (date-time)

ApplicationServices

The managed state one application environment owns, verbatim from the platform: the three services' logical resource names and credential references, each with its recorded state. The environment is the deployment's own selection; another customer's application is not one this deployment holds.

applicationId *
string
environment *
dev, stage, prod
services *
array of ManagedService

ApplicationServiceSetup

The one field setup names: the environment to set up. The names and references are the platform's derivation, never body fields.

environment *
dev, stage, prod

ApplicationSecret

The secret's name and value, carried once. The value is held by the secret manager and never recorded, returned or echoed; the answer carries the reference instead.

environment *
dev, stage, prod
name *
string
value *
string

The value the secret manager holds, never recorded or echoed

SecretReference

What recording a secret answered: the secret's name and the reference the platform derived and the secret manager holds. The value is never in the answer and never in any record.

name *
string
reference *
string

ApplicationPlan

The visible, editable plan: topology only, never authority. No field grants a tool, source, destination or credential — necto.yaml remains the only authority source for governed components. The plan carries the deployment's tenant key; no request field selects it.

customerId *
string

The deployment's tenant key

source *
PlanSource
components *
array of PlanComponent
toolchain *
PlanToolchain
commands *
PlanCommands
network *
PlanNetwork
schedules *
array of PlanSchedule
resources *
PlanResources
dependencies *
array of string
destinations *
array of PlanDestination
runtimeTier *
standard, governed

The requested runtime tier; placement is computed later

PlanSource

The source revision and build context, or the digest that is the source for a prebuilt image — topology, never authority.

kind *
repository, dockerfile, oci_digest
revision
string
buildContext
string
imageDigest
string

sha256:<hex>; the immutable identity a prebuilt image is addressed by

PlanComponent

A component's name and kind, and the one profile identity a governed component carries — a name, never the compiled profile.

name *
string
kind *
web, api, worker, static
profile
string

PlanToolchain

The detector, builder and base-image identifiers. Identifiers, never measurements: synthetic adapter input records synthetic-adapter-input and claims no version.

detector *
string
builder *
string
baseImages *
array of string

PlanCommands

build *
array of string
start
string

PlanPort

port *
integer
protocol
string

PlanHealthCheck

path *
string
intervalSeconds *
integer

PlanSchedule

A command with no schedule is a worker; one with a cron expression is a schedule

name *
string
command *
string
schedule
string

PlanResources

Resources and scaling bounds

cpuCores *
number (double)
memoryMib *
integer
minInstances *
integer
maxInstances *
integer

PlanDestination

A declared external destination for a standard component — a bare host name, topology only. It grants no reachability and routes no traffic.

host *
string

ApplicationPlanVersion

One immutable plan version: the plan, its content hash and parent, and the winning source of every field. The hash is minted from the tenant key, the plan and the parent, so a version is never re-labelled and a correction is a new version, not a change to this one.

applicationId *
string
versionHash *
string

sha256:<hex> over the tenant key, the plan and the parent

parent
string

The version this one corrects; absent for the first version

provenance *
object

Every plan field's winning source, keyed by the plan's field name — the plan's own precedence display.

PlanFieldProvenance

Where a field's winning value came from, verbatim from the plan's own precedence: the explicit repository file, the saved project setting, or the detector default. Every displayed field carries one.

ApplicationPlanDetection

The detection input. The application's registered source selects the construction path: adapterInput is the detector's output for a repository or template source, dockerfile is the file's text for a Dockerfile source, and a prebuilt image has neither — its digest is the source and the build is skipped. The detector output is adapter input, never the public schema: it is read against a closed contract, and a key outside it is refused rather than ignored. saved is the saved project setting level, read against the closed necto.app.yaml schema.

adapterInput
object

The detector's output for a repository or template source, read against the adapter's closed contract — never the public schema and never persisted as such.

dockerfile
string

The Dockerfile's text for a Dockerfile source; at most the minimal read (FROM, EXPOSE, CMD) resolves a value, and anything ambiguous is refused.

saved
object

The saved project setting level, read against the closed necto.app.yaml schema; an unknown key is refused.

ApplicationPlanCorrection

A customer correction of the current plan version. values is the closed necto.app.yaml schema — the correctable plan fields. A key outside it is refused as unknown; toolchain and source are deliberately not correctable, and customer_id is refused like any other unknown key because the deployment supplies the tenant key.

values *
object

The closed necto.app.yaml value set

ApplicationGuardrails

The read-only graph and the default guardrails for one application's accepted plan version. Every field states the source it came from and the accepted plan version it was read at: a graph field's source is the plan's own per-field provenance, a guardrail field's source is the governed profile (or default when none governs), and the placement's source is computed. The plan is topology only and grants no authority — no graph field is compiled into a guardrail.

applicationId *
string
planVersion *
string

The accepted plan version hash every field was read at

profileHash
string

The governed profile's content hash when one governs the application; absent when none does

fields *
array of GuardrailField
refusals
array of GuardrailRefusal

GuardrailField

One graph or guardrail field, its value, the source the value came from and the accepted plan version it was read at.

name *
string

The plan field's camelCase name for a graph field, or the guardrail's own dotted name (guardrails.actionAllowlist, guardrails.egressAllowlist, guardrails.credentialScopes, placement)

value *
any
source *
string

The value's winning source: the plan's per-field provenance level for a graph field, governed profile for a compiled guardrail, default when no profile governs, or computed for the placement. Never a plan field standing in for authority.

version *
string

The accepted plan version hash this field was read at

GuardrailRefusal

A refusal the read made visible: the field it concerns, the policy decision's own reason, its safe metadata and the accepted plan version it was read at — never a generic application error.

name *
string
reason *
string

The policy decision's own named reason

field
string

The profile field a compile refusal names

detail *
string
version *
string

ApplicationDeployment

One deployment of the application's accepted plan. The plan version is named by its content hash and the image by immutable digest; the environment is the deployment's dev environment, and the governed profile hash and compiled-artifact hashes on the record are the platform's own derived values, never body fields.

acceptedPlanHash *
string

The accepted plan version's content hash

imageDigest *
string

sha256:<hex>; the built image this deployment runs

tests
array of object

Test-name and artifact-URL identifier pairs

DeploymentRecord

Every deployment's automatic record: metadata only — never source, prompt, response, secret or payload content.

deploymentId
string
actor
string
application
string
environment
dev, stage, prod
sourceRevision
string
detectorVersion
string
builderVersion
string
acceptedPlanHash
string
imageDigest
string
tests
array of object

Test-name and artifact-URL identifier pairs

recordedAt
string (date-time)
outcome
string
profileHash
string
artifactHashes
array of object

AgentCard

The agent card's field set is fixed

agentId
string
owner
string
purpose
string
dataSources
array of string
allowedTools
array of string
environment
string
currentVersion
string
currentPolicyHash
string
recentRuns
array of RunSummary
deniedRequests
array of DeniedRequest

Returned alongside runs, never behind a filter

approvals
array of ApprovalRecord

RunSummary

runId
string
startedAt
string (date-time)
outcome
drafted, approved, sent, refused, failed
citationCount
integer
uncited
boolean

An uncited draft cannot be promoted to a send

trigger
RunTrigger

RunTrigger

The schedule tick a governed run was started from. Absent on a run that was started without a trigger, which reads as absent rather than as a fabricated tick.

kind *
schedule
caller *
string

The verified client principal that started the run, never a value the run's own request supplied.

taskId *
string
scheduledAt *
string (date-time)

ScheduleTriggerRequest

A schedule tick a registered machine client starts a run from.

kind *
schedule
taskId *
string
scheduledAt *
string (date-time)

The tick's instant, timezone-aware

key *
string

The tick's canonical key, recomputed from the task id and the scheduled instant; a value that does not match is refused.

ScheduleEventRequest

A tick a scheduler observed but did not fire, reported so it is recorded rather than silent.

kind *
skipped
reason *
missed
taskId *
string
scheduledAt *
string (date-time)

The tick's instant, timezone-aware

key *
string

The tick's canonical key, recomputed from the task id and the scheduled instant; a value that does not match is refused.

DeniedRequest

at
string (date-time)
caller
string
kind
retrieval_denied, egress_blocked, credential_expired, scope_refused
reason
string

ReviewBundle

One period of one agent's evidence, as an auditor reads it. Built only from TimelineEntry and release identifiers: there is no field here a document body or a raw prompt could occupy. Plain JSON, readable without Necto tooling.

bundleVersion *
string

The bundle format, so a reader can tell versions apart

exportId *
string
agentId *
string
exporter *
string

The verified subject that asked for the export

exportedAt *
string (date-time)
periodFrom *
string (date-time)
periodTo *
string (date-time)
retentionCutoff
string (date-time)

Nothing before this is guaranteed to still exist

complete *
boolean

False when the period begins before the retention cutoff

incompleteReason
string
entryCount
integer
releases
array of object
runs
array of object

TimelineEntry

Immutable. Identifiers and decisions only: no document text, no raw prompts, no credential values.

at *
string (date-time)
kind *
sandbox_created, sandbox_resumed, sandbox_timeout, sandbox_killed, sandbox_start_denied, sandbox_start_failed, sandbox_exec_failed, run_finished, sandbox_stop_failed, scope_checked, retrieval_decided, candidate_dropped, credential_injected, draft_produced, approval_decided, send_attempted, action_replay_refused, export_created, containment_triggered, workload_stopped, authority_revoked, service_restored, identity_asserted, authority_resolved, authority_granted, authority_withdrawn, release_decided, standing_approval_revoked, ttl_exception_granted, credential_withdrawn, api_request, caller_first_seen, promotion_requested, promotion_decided, promotion_executed, schedule_fired, schedule_skipped, schedule_refused

sandbox_resumed is reserved and never emitted: the runtime interface has no resume operation. Do not branch on it.

evidenceClass
semantic, infrastructure

The entry's own class: semantic for a kind the four behavioural dimensions read, infrastructure for every other kind the record names. A word, never a number or a score. An entry presented as semantic agent evidence is checked before it is presented, and a refusal names the kind.

issuer
string

Which identity provider vouched for caller. A sub is unique only within its issuer, and a later dispute needs to know which provider asserted it. Under federation this is the origin, not the console, so a federated login's entry names the provider that actually asserted it.

customerId *
string

The third part of the authority key. The same human may exist under more than one customer.

agentId
string

The agent an entry is about, where that is not implied by the run it belongs to. A containment event is the case that needs it: stopping an agent is not part of a run, and "which agent was contained" is the first question an incident review asks.

workloadId
string

The workload a lifecycle entry is about. An identifier the substrate issued, never a substrate handle.

profileHash
string
releaseVersion
string
grantId
string

The standing approval a revocation entry ends. An identifier of the grant, never its channel, message or approver.

artifactDigest
string

The prebuilt artifact the workload started from: an E2B template build id (a UUID), built outside the platform process, never a template name, alias or tag. The released identifier the sandbox was actually created from, so "what ran" is answerable from the record rather than from the release store's current state.

owner
string

Who owns the agent, carried from the compiled metadata. Distinct from caller, which is whoever made the request an entry records.

caller
string
taskId
string

The scheduled task a schedule_fired, schedule_skipped or schedule_refused entry is about. It names the task whose tick did or did not run.

scheduledAt
string (date-time)

The tick's scheduled instant, timezone-aware. A tick is an instant, not a period.

key
string

The tick's canonical key, recomputed from the task id and the scheduled instant. A repeated key never starts a second run. Absent on a tick refused before it could have run: no key names a tick that has not happened.

purpose
string

The declared purpose the query ran under. A decision is not reconstructable without it.

environment
string

The environment the query ran in. Staging never reaches a production source; the record proves which one asked.

application
string

The application the record is about, from the deployment record's (customer, application, environment) triple. An identifier, never the application's source, configuration or content.

component
string

The plan component within the application the record is about. A component name, never the component's command, image or environment.

deploymentId
string

The deployment record the entry is about: the id a promotion approval records as its target head and the deployment store compares. An identifier, never the deployment's actor, plan or artifact.

connector
string

Which connector made the source decision. An identifier, never a credential or an endpoint.

documentId
string
documentVersion
string
location
string
decision
allow, deny
reason
string

Required on a deny. A silent drop is indistinguishable from a retrieval miss and destroys the audit trail. On a tick refused before it could have run it is future.

tokenId
string

Identifier only, never the token value

envelopeId
string

The action envelope an entry is about. An approval binds the envelope's hash, so a reviewer asking whether an action was executed, and executed once, has it in hand. The id is what joins send_attempted to action_replay_refused.

queryHash
string

Salted

citations
array of string

The citation set, as document@version#location identifier triples: the passages a step relied on, never the passages themselves.

upstreamIdentityPath
array of string

An identity_asserted or authority_resolved entry's whole upstream identity path, as issuer#subject#verification_basis identifier triples, at most two, origin first. verification_basis is one of id_token_verified, broker_verified_origin or broker_authenticated. issuer and caller above already name the first entry; this carries the rest of the chain. Absent for a non-federated login.

method
string

HTTP method of the request an api_request entry records. An identifier of the call, never the call itself.

pathTemplate
string

The matched route's path template, such as /agents/{agentId}/card, never the concrete path. Absent on an unmatched route, where only the concrete path exists.

routeName
string

The matched route's name. unmatched when no route matched: an unmatched route is a refusal, and its record has to say which path could not.

status
integer

The response status the request produced.

outcome
served, refused, failed

The refusal/fault taxonomy, on api_request entries: served is a 2xx; refused is Necto considering a request and saying no (the 4xxs); failed is Necto unable to answer (the 5xxs). Not decision's retrieval enum.

latencyBucket
under_100ms, 100ms_1s, 1s_10s, over_10s

Coarse response latency, an ordered bucket, not the raw duration.

exitCode
integer

The artifact entrypoint's own exit result, recorded unchanged. Present only on a run that ended: 0 ran, and the entrypoint's own non-zero code is the entrypoint's own result, not a platform refusal.

stdoutBytes
integer

The number of UTF-8 bytes the entrypoint wrote to stdout, never the output itself. Present only on a run that ended, so a silent refusal is visible without any output reaching the record.

stderrBytes
integer

The number of UTF-8 bytes the entrypoint wrote to stderr, never the output itself. Present only on a run that ended, so a silent refusal is visible without any output reaching the record.

RegimeKey

The release regime a baseline and an observation are keyed to: one agent, one compiled profile and one release. A release opens a new regime; a prior regime's baseline is kept for comparison and never scores the next one.

agentId *
string
profileHash *
string
releaseVersion *
string

BehaviourRange

A closed range. Both ends are included.

minimum *
number
maximum *
number

BehaviourRefusal

One refused attempt, carrying the rule that refused it. A refusal is an observation, never an absence: an attempt the platform refused is returned here rather than dropped from the comparison. Identifiers only, never content, passages, prompts or credentials.

identifier
string

The document, destination or tool identifier the attempt named, when the record names one.

rule *
string

The rule that refused the attempt, as the record states it.

BehaviourCollection

One document a run read, or was refused.

name *
string
result *
read, refused
rule
string

The rule that refused the read; absent when it was read.

BehaviourDestination

One destination a run contacted, or was refused.

name *
string
result *
sent, refused
attempts
integer

The number of attempts the run's own record carries with this name and result: the send attempts the platform allowed for a sent destination, and the attempts the named rule refused for a refused one.

rule
string

The rule that refused the send; absent when it was sent.

BehaviourComparison

One dimension's comparison verdict. neutral when the run's values are inside the regime's baseline; review when they are outside it or an attempt on the dimension was refused. Never a number.

verdict *
neutral, review
reason
string

Plain words for a review verdict; absent when neutral.

RegimeTransition

Where one regime's baseline ended and the next began, derived from the agent's own run records.

endedAt *
string (date-time)
ended *
RegimeKey
beganAt *
string (date-time)
began *
RegimeKey

BehaviourBaselineInformation

The documents a usual run reads, and how many it opens.

documents *
array of string
recordsPerRun
BehaviourRange

Absent while the regime has no observed run.

BehaviourBaselineDestinations

The destinations a usual run contacts.

destinations *
array of string

BehaviourBaselineTools

The tool order a usual run follows.

order *
array of string

BehaviourBaselineVolume

The records-per-run and run-seconds a usual run shows.

recordsPerRun
BehaviourRange

Absent while the regime has no observed run.

runSeconds
BehaviourRange

Absent while the regime has no observed run.

BehaviourBaseline

What is usual for one agent in one release regime. state is ready when the regime has enough observed history to compare against, and insufficient_volume when it does not: the state scores nothing, and no verdict is returned rather than a low one.

agentId *
string
regime
RegimeKey

Absent when no run names a regime yet.

state *
ready, insufficient_volume
reason
string

Why the state is insufficient_volume; absent when ready.

observedRuns *
integer

The number of the regime's finished runs this baseline summarises. The platform holds no separate review record, so this is an observation count and never a claim that a person reviewed each run.

minimumVolume
integer

The configured minimum the state is read against; absent when the deployment configures none, and then the state is insufficient_volume rather than ready.

transitions
array of RegimeTransition

BehaviourInformation

The documents one run read, or was refused.

collections *
array of BehaviourCollection
records
integer

How many records the run opened.

BehaviourDestinations

The destinations one run contacted, or was refused.

destinations *
array of BehaviourDestination

BehaviourTools

The tools one run used, in order, and those refused.

order *
array of string
refusals
array of BehaviourRefusal

BehaviourVolume

The records one run opened and the seconds it ran.

records *
integer
seconds
number

Absent when the run has no recorded end.

BehaviourObservation

One run's observed behaviour on the four dimensions, compared with the baseline of the regime that run belongs to, as it stood before the run started. Only that regime's runs that finished before this run started count. Refused attempts are observations carrying the rule that refused them, never dropped. state is insufficient_volume when the regime has too little observed history to compare against, and then comparison is absent: the run scores nothing rather than badly.

runId *
string
agentId *
string
regime
RegimeKey

The regime this run was compared against.

state *
ready, insufficient_volume
reason
string

Why the state is insufficient_volume; absent when ready.

observedRuns *
integer

The number of the regime's runs that finished before this run started, which the comparison baseline summarises. The platform holds no separate review record, so this is an observation count and never a claim that a person reviewed each run.

minimumVolume
integer

The configured minimum the state is read against; absent when the deployment configures none.

destinations *
BehaviourDestinations
comparison
BehaviourComparisons

Absent while the state is insufficient_volume.

AuthorityCheck

tool *
string
allowed *
boolean
authority
read_only, draft_only, write_reversible, write_external

Present when allowed is true

reason
string

Required when allowed is false

TaskCompileResult

narrowed *
boolean

Always true: a 200 is returned only once every axis is confirmed a subset of the target agent's compiled artifacts. A response that would carry false is a 422 Problem instead.

agentId *
string
agentVersion
string

The agent's last approved release version, when one exists

data *
array of string

The task's own narrow.data, already confirmed a subset

tools *
array of string

The task's own narrow.tools, already confirmed a subset

authority *
read_only, draft_only, write_reversible, write_external

The task's own requested authority

droppedData *
array of string

The profile's data minus the task's narrow.data

droppedTools *
array of string

The profile's tools minus the task's narrow.tools

grantedAuthority *
read_only, draft_only, write_reversible, write_external

The profile's own authority ceiling

scheduled
object

Present only when the applied task carried a schedule. The recorded schedule carries its invocation beside the narrowing, so a scheduler can reconstruct the run without the task file.

TaskSummary

One recorded kind: Task apply that carried a schedule. The record carries the compiled invocation (input, deliver, authority, narrowed data/tools) and the tenant key, so a scheduler can reconstruct the run.

customerId *
string

The deployment's own tenant key, recorded with the task. Deployment-supplied, never taken from a request, claim or header.

taskId *
string
agentId *
string
schedule *
string

Five-field cron in UTC, validated by the standard-tier trigger validator at compile. A malformed schedule is refused by name, never recorded.

input *
string

The task's own input, kept for the scheduler

deliver *
string

The task's own deliver, kept for the scheduler

authority *
read_only, draft_only, write_reversible, write_external

The task's own requested authority

data *
array of string

The task's narrowed narrow.data

tools *
array of string

The task's narrowed narrow.tools

createdAt
string (date-time)

MutationRecord

One collaboration mutation, as the append-only journal recorded it: the actor, the organisation, the application, the object kind and id, the action and the resulting version. Every field is an identifier — never a comment body, prompt or payload. A clone lands as a plan_version, never as a kind of its own.

customerId *
string

The deployment's own tenant key, recorded with the mutation. Deployment-supplied, never taken from a request, claim or header. The read is already tenant-keyed; the key is carried so the record says what it is keyed by.

organisationId *
string

The first-class organisation the mutation belongs to, recorded beside the tenant key. Strictly inside the customer, never a second tenant axis; the record carries it so a caller can tell which organisation a mutation belongs to.

actor *
string

The mutation's assertion-resolved subject

application *
string
objectKind *
comment, plan_version, role_grant, membership, component
objectId *
string
action *
created, cloned, granted, revoked
resultingVersion *
string
at *
string (date-time)

HistoryEntry

One derived version-history entry: the anchor kind, the identifier its store knows it by, the time the store recorded and the actor where the store has one. at and actor are absent where the owning store has neither — a plan version carries no clock and no actor, a run record carries no actor. parent is a plan version's lineage by hash. Nothing here is invented to fill a gap.

anchor *
plan_version, deployment, release, run
identifier *
string
at
string (date-time)
actor
string
parent
string

WhoAmI

caller *
string

The stable subject identifier this caller resolved to. Under federation this is the origin entry's sub on the assertion, not the console's.

customer *
string
role *
string
approvalAuthority
boolean

Whether this caller may approve a release hash

FederationProvider

One record of the committed federation registry, rendered for the console server that reconciles its provider list against it. Identifiers and display fields only, never a secret: the upstream issuer, the record's Necto-side customer id, the named endpoint hosts and the allowed verification bases. No client credential, domain or endpoint path is in the record or the answer.

issuer *
string (uri)

The upstream identity provider's issuer identifier, as the record holds it.

customerId *
string

The Necto-side customer the record is for; every record this deployment answers carries the deployment's own.

jwksHost *
string

The named host serving the issuer's JWKS

discoveryHost
string

The named discovery endpoint host, when configured

tokenHost
string

The named token endpoint host, when configured

userinfoHost
string

The named user-info endpoint host, when configured

allowedVerificationBases *
array of string

The verification bases the record allows, e.g. id_token_verified; the console's own basis must be among them.

ObservedDestination

One refused destination, identifier-only. The destination's own identifiers and the platform's own instants, plus the bounded attempt counter; no payload, content or capture field exists. The tenant key, the run's governed-component identity and the run come from the deployment and the verified workload claims, never from a request body. The attempt counter is a bounded count, never a score. A repeat observation advances lastSeenAt and never moves observedAt; capReached marks the run's single cap-reached record, written once when a new destination arrives past the per-run cap.

destinationId *
string

The stable identifier for this observed destination; the decision record and the customer-plane routes name it.

customerId *
string

The deployment's own customer id

applicationId *
string

The run's governed-component identity -- the profile name the run route resolved -- never a body field

runId *
string

The run whose egress refused the destination

host *
string

The named host the run tried, normalized; never a wildcard, a CIDR, a scheme, a port or a path

port *
integer
protocol *
tcp, tls
observedAt *
string (date-time)

The platform's own instant at the first observation; a repeat counts and never moves it

lastSeenAt *
string (date-time)

The platform's own instant at the most recent observation; a repeat advances it

attempts *
integer

The bounded attempt count; a repeat observation coalesces into the row and increments it up to the platform's cap

capReached *
boolean

The run's one cap-reached record: true when a new destination arrived past the per-run cap, so the refusal list is bounded and the cap is recorded rather than sampled