HTTP API reference
Compatible native/API origin: https://forge.readyit.dk. The browser product
uses https://opalspan.com; the old host remains for installed clients.
Routes below are relative to the compatible origin.
Authentication, selection and JSON
Human application requests use a valid bearer access token for the service and the
X-Forge-Workspace-Id header selecting an authorized workspace UUID.
Use the supported login flow; do not extract credentials from a profile or
paste tokens into documentation, logs or customer messages.
GET /api/v1/tenants discovers the authenticated identity's memberships
before selection. The header selects among existing memberships; it grants
no authority. Human Admins and Technicians have ordinary workspace-wide
access under the current workspace-humans-v2 policy. Settings, accounts,
roles and invitations remain Admin-only.
Machines retain explicit test scope and are denied human directory, case, overview and generic history APIs. A human role claim does not turn a machine principal into a human.
Send request objects as application/json. Wire fields are snake_case; UUIDs
are strings and timestamps use ISO 8601 with an offset. Nullable/optional
response fields may be omitted. DTO names below identify documented wire
contracts, not URLs or assembly-download instructions.
HTTP responses contain their actual object, array or page—not the CLI
ok/data/next_actions envelope. Treat customer/history responses as private
and honor Cache-Control: no-store. After a mutation times out, its completion
may be unknown; do not assume it is safe to replay.
Connected work history and explicit diagnostic plans
| Method | Route | Contract |
|---|---|---|
| GET | /api/v1/activity-history |
WorkbenchActivityHistoryPage; bounded q/actor/device/caseId/company/contact, since/until/filter/scope/kind/cursor/limit; exact query names follow WorkbenchHistoryApi. |
| GET | /api/v1/activity-history/{conversation}/{run} |
WorkbenchHistoryRun; exact frozen context/observed lifecycle, private handback only under conversation authority, and bounded operation/access children. |
| GET | /api/v1/case-history/{id} |
WorkbenchCaseTimelinePage; prospective Case/draft chronology, kind/period/cursor/limit filters and explicit historical coverage. |
| GET | /api/v1/diagnostic-plans/devices/{id} |
DeviceDiagnosticCapability; actual platform/helper availability, sections and limitations. No collection. |
| POST | /api/v1/diagnostic-plans/prepare |
DeviceDiagnosticSelection → private exact DeviceDiagnosticPlan. No access acquisition or collection. |
| POST | /api/v1/diagnostic-plans/start |
StartDeviceDiagnosticPlan → exact DeviceDiagnosticRun; explicit collection under current native authority. |
| POST | /api/v1/automations/{id}/runs/{run}/continue-recorded |
Original run owner and frozen configuration/targets; UUID request_id and original configuration_version as expected_version. Existing /continue remains current-definition based. |
History has stable typed source identities. Access-period context is never
promoted into an individual run result. Frozen uncertain_effects and uncertain
terminal state remain visible; old unrecorded facts are not reconstructed.
Grouped usage labels can contain private accepted intent; keep complete reports
private, not in operational logs.
Diagnostic start binds a private review token to actor/workspace, target/Case revision, signed helper and expiry. Collection invokes no model and changes no Monitoring configuration. The exact UUID reconciles a lost acknowledgement; never automatically replay an uncertain collection. Current device-user consent, work allowances and agreement/monetary checks remain authoritative. Optional interpretation is a separate explicit request.
The additive startup source contract uses GET
/api/v2/fleet/devices/{uuid}/startup-evidence →
Forge.Contracts.V2.DeviceStartupEvidenceRead under the same human device
permissions. It returns original native duration/period/provenance, latest
collection coverage and an explicitly comparable prior baseline, never uptime
or a telemetry-derived guess. Canonical refresh requires the existing plan with
sections: ["startup"]; existing profiles exclude it. Old signed helpers remain
unsupported until compatible helper publication. See
explicit startup source semantics;
source availability is not a claim that new helpers or Bridge are deployed.
Automation and telemetry
| Method | Route | Purpose |
|---|---|---|
| GET | /api/v1/automations, /{id}, /limits, /templates, /models |
Bounded configuration, current offer capabilities and model/template choices. ID suffixes are under /api/v1/automations. |
| POST | /api/v1/automations/quote, /confirmations |
AutomationConfiguration target aggregate or AutomationPrepareWrite exact private human review; neither approves or reserves work. |
| GET | /api/v1/automations/confirmations/{id} |
Original actor's exact AutomationHumanReview and decision state. |
| POST / PUT | /api/v1/automations, /{id} |
Apply exact AutomationWriteRequest with consumed human confirmation; changed payload/revision conflicts. |
| POST | /api/v1/automations/{id}/pause, /resume, /adopt, /delete |
AutomationActionRequest; resume/adopt prepare at /{id}/confirmations/{operation}. |
| GET | /api/v1/automations/{id}/runs, /runs/{run} |
Persistent-conversation wake snapshots, bounded results and distinct AI/new/reused work accounting. |
| POST | /api/v1/automations/{id}/runs/{run}/answer, /cancel, /continue |
Answer exact waiting decision, cancel exact run, or start explicit new run without uncertain-effect replay. |
| GET | /api/v1/automations/{id}/runs/{run}/notifications |
Private metadata-only outbox/delivery state. |
| GET / POST | /api/v1/automations/notification-settings |
Current member's critical-service email preference/revision. |
| POST | /api/v1/automations/critical-email/{conversation}/{run}/prepare, /send |
Exact AutomationCriticalEmailRequest review, then AutomationCriticalEmailSend; verified workspace members only, not a mailbox-delivery assertion. |
| GET | /api/v1/device-telemetry/signals, /activation, /instruction |
Allowed signals and exact configured notice/instruction applicability, not consent. |
| GET | /api/v1/device-telemetry/devices/{id}, /history |
DeviceTelemetryView[] or bounded DeviceTelemetryHistory; stored metadata, no fresh diagnostic work. |
| POST | /api/v1/device-telemetry/devices/prepare, /devices |
DeviceTelemetryChange; exact single/bulk revisions and private human confirmation. |
| GET / POST | /api/v1/device-telemetry/new-enrollment-default |
Admin-only future-enrollment default; prepare at /prepare, never changes current devices. |
| POST | /api/v1/device-telemetry/instruction/prepare, /instruction, /consent/prepare, /consent |
TelemetryProcessingInstruction or DeviceTelemetryConsent, attributable exact holder/device-user review; missing consent/necessity remains off. |
The /browser/v1/automations/... adapter requires the genuine cookie-authenticated
human and CSRF header for unsafe requests. Only that protected human path or the
existing exact Mech controller may answer a configuration review. A bearer client
can prepare/read/apply a receipt but cannot manufacture human approval. Existing
/api/v1/mech/conversations/{id}/decisions remains the separate pending-runtime
decision read/answer/status adapter; it does not create a new confirmation engine
or grant a role.
AutomationRunView identifies its configuration revision, current owner, actual
targets, trigger, status/reason and accounting. work_session_budget_minor is a
zero-default whole-run DKK monetary authority, not AI credits or usage. Native
accepted-operation recording owns actual sessions; configuration/claim creates no
charge. A telemetry receipt acknowledges metadata only; it is not verified repair,
consent, diagnostic completion or a credit/work-session charge. Rescue is excluded. Current catalogue discovery includes telemetry-light-v1 and
telemetry-full-v1, with cumulative native capability and independent privacy/local
consent gates. Source support is not a claim of deployed activation.
Workspace billing
| Method | Route suffix | Purpose |
|---|---|---|
| GET | /status, /work-sessions, /budget |
Retained subscription/capacity, actual and provisional work, accepted/in-flight monetary exposure. |
| POST | /compare, /session-quotes, /budget-quotes |
Full-month applicable-plan comparison, actual target-set exposure, or exact budget proposal; never approval. |
| GET | /confirmations/{id} |
Original Admin's retained quote and human-confirmed state. |
| POST | /hosted-subscriptions, /device-slots, /native-top-ups |
Explicit request-bound hosted purchase; returns private provider handoff, not payment evidence. |
| GET | /hosted-subscriptions/{id}, /device-slots/{id}, /native-top-ups/{id} |
Original retained/provider-confirmed purchase outcome. |
| POST | /customer-portal, /plan-change-portal |
Stripe invoice/billing/cancellation or validated same-generation target-plan handoff. |
plan-change-portal accepts {} for the retained two-plan route or
{"target_plan_id":"fleet"} for an explicitly configured target. Caller-selected
prices, provider accounts, subscriptions and return URLs are refused. The bearer
boundary has no budget-confirmation endpoint. The exact authorized human confirms
through the protected browser or Mech controller, where workspace/run/quote/expiry
and current authority are rechecked atomically. Provider Checkout/card/SCA/consent
and Auth0 signup/sign-in remain browser flows. No machine principal gains billing
administration. Human-operated CLI is not required.
Public downloads
These GET routes require no login:
| Route | Result |
|---|---|
/api/v1/client-configuration |
ClientConfigurationDto: public bridge_url, auth0_issuer, auth0_client_id, auth0_audience, expected_publisher. No user credentials or memberships. |
/api/v1/cli/releases/latest?platform=win-x64 |
CliReleaseDto: schema_version, version, platform, expected_publisher, installer, archive. Each artifact contains url, size, sha256. |
/api/v2/cli/releases/latest?platform=linux-x64 |
CliReleaseV2Dto: numeric schema_version, version, platform, signed package, target_payloads. |
/api/v2/cli/releases/linux-x64/{version} |
Metadata for one verified Linux OpalSpan Agent CLI release under its retained signed release identity. |
/download/cli/win-x64/latest/installer |
HTTP 200 verified file (503 when unavailable) to the selected Windows setup. |
/download/cli/win-x64/latest/archive |
HTTP 200 selected verified Windows ZIP; 503 when unavailable. |
/download/cli/linux-x64/latest/installer |
HTTP 200 verified file (503 when unavailable) to the selected Linux setup. |
/download/cli/linux-x64/latest/archive |
HTTP 200 selected verified Linux archive; 503 when unavailable. |
/download/cli/linux-x64/latest/manifest |
Redirect to version-specific Linux release metadata. |
PortableArtifactEnvelopeDto contains schema_version, algorithm, key_id,
manifest and signature. The manifest/signature encode exact signed
artifact identity, not a new trust key. Clients must verify with the approved
independently pinned key, not trust arbitrary keys supplied beside a download.
Use supported setup rather than implementing partial signature validation.
Installer/archive aliases select capable Windows/Linux releases, not necessarily legacy latest catalogue entries. When downloading several artifacts, resolve a single release and use its immutable locations; independent latest requests can straddle a release change. Unavailable downloads fail explicitly.
Identity and enrolled devices
| Method | Route | Response |
|---|---|---|
| GET | /api/v1/tenants |
Array of TenantMembershipResponse. |
| GET | /api/v1/me |
TechnicianIdentityResponse for the selected workspace. |
| GET | /api/v1/devices |
Optional exact company UUID; array of authorized DeviceSummaryResponse, filtered in the same workspace query. |
| GET | /api/v1/devices/{id} |
Authorized DeviceDetailsResponse. |
| PUT | /api/v1/devices/{id}/metadata |
Updated device details; input UpdateDeviceMetadataRequest. |
Key fields:
TenantMembershipResponse:tenant_id,name,role,current, optional legacyauth0_organization_id. Use the OpalSpan workspace ID for selection.TechnicianIdentityResponse:id,subject,display_name, optionalemail,tenant_id,tenant_name,role,device_access_mode.- Device summaries:
id,machine_name,device_identifier,presence,last_seen_at,host_version,is_revoked,platform, optional descriptive labels and connection/transport metadata. - Device details add revocation, current lease/technician and SSH identity information. Compatibility fields do not imply a supported legacy transport; the released path is native relay.
UpdateDeviceMetadataRequest: nullabledisplay_name,primary_user_name,organization_name, and observedversion. These do not update company/contact relationships.
Use opalspan device connect, exec, upload and download for remote work.
The CLI owns key handling, exclusive leases, grant renewal and relay
admission. There is no generic HTTP “execute a command” API in this reference.
Device administration and enrollment housekeeping (S4)
| Method | Route | Contract |
|---|---|---|
| PUT | /api/v1/devices/{id}/work-hours |
SetDeviceWorkHours: schedule, expected_version → advisory DeviceWorkGuidance. DeviceEditor. |
| PUT | /api/v1/devices/{id}/capacity |
SetCapacityActiveRequest: active, version. Admin only. |
| POST | /api/v1/devices/{id}/revoke |
RevokeDeviceRequest: reason, version; closes live control, retains history. DeviceManager. |
| GET | /api/v1/device-transfers/{id} |
Metadata-only DeviceTransferResponse with version; device ID is independently authorized. DeviceManager. |
| GET | /api/v1/enrollment/options |
EnrollmentOptionsResponse: platforms, share_issuance_enabled; verified current release choices. DeviceManager. |
| GET | /api/v1/enrollment |
EnrollmentTokenResponse[], lifecycle and version; never codes or recoverable URLs. DeviceManager. |
| GET | /api/v1/enrollment/{id} |
Exact authorized EnrollmentTokenResponse; transferred device IDs are suppressed. DeviceManager. |
| POST | /api/v1/enrollment/{id}/revoke |
EnrollmentVersionRequest: version; unused token only, not a completed device. DeviceManager. |
| POST | /api/v1/enrollment-shares/{id}/revoke |
EnrollmentVersionRequest: version → metadata-only EnrollmentShareResponse. DeviceManager. |
| POST | /api/v1/enrollment-shares/{id}/extend |
ExtendEnrollmentShareRequest: expected_expires_at, additional_minutes, version. Admin only. |
Device details, enrollment and share status add opaque version fields.
New agent clients preserve the observed revision; compatible legacy optional
fields keep their meanings. Revisions exclude heartbeats and lease renewal.
Work hours use their independent work_guidance.version, including none;
they remain advisory, not an execution policy or scheduling promise.
Stale versions return 409 stale_version. Share extension also compares the
previous exact expiry, never revives a used/revoked/run-owned link or rotates its
URL/code/pinned release. GETs return no-store metadata. No client automatically
retries an uncertain mutation.
Mech uses the same service transaction and shared S3 confirmation engine for
consequential changes, binding actor/workspace, exact target/version/payload,
conversation/run/scope and expiry. There is no public DTO approval field or
borrowed CLI device lease. Workspace Rescue history remains
GET /api/v1/temporary-devices[/{id}]; read visibility grants no owner-only
control/cleanup credential. Enrollment receipts and reconnects are not an
additional charged work session. Transfer status never returns acceptance or
candidate credentials, and prepared does not mean completed. Windows/Linux share
all server paths; macOS remains a technical preview with unchanged native trust.
Team, invitations and workspace settings
| Method | Route | Contract |
|---|---|---|
| GET | /api/v1/team |
TeamMemberResponse[], including opaque version, effective access, capacity and the existing AI limit. |
| GET | /api/v1/team/{id}/device-access |
DeviceAccessResponse with the same member version. |
| GET | /api/v1/team/{id}/device-access/assignments?limit=25&cursor=...&version=... |
MemberAssignmentPage: stored/effective modes, current policy/edit restriction, assignment-specific version, exact page/total and final completeness proof. |
| POST | /api/v1/team/{id}/device-access/review |
ReviewMemberAssignmentsRequest: exact version/modes and additions/removals; complete-set mode replacement additionally requires verified traversal proof and ≤1,000 IDs. |
| POST | /api/v1/team/{id}/device-access/apply |
Original request_id and actor/workspace/member-bound review_token; exact idempotent application or status-only replay with current authority. |
| PUT | /api/v1/team/{id}/role |
SetRoleRequest: role, version. |
| PUT | /api/v1/team/{id}/spend-limit |
SetMemberSpendLimitRequest: monthly_credits, version; null removes the member-specific limit. |
| PUT | /api/v1/team/{id}/capacity |
SetCapacityActiveRequest: active, version. |
| PUT | /api/v1/team/{id}/device-access |
SetDeviceAccessRequest: access_mode, device_ids, version; v2 human scope remains policy-managed. |
| DELETE | /api/v1/team/{id}?version={version} |
Removes the exact current member; the last active Admin is protected. |
| GET | /api/v1/team/invitations |
Metadata-only TeamInvitationResponse[], lifecycle and versions. |
| POST | /api/v1/team/invitations/issue |
IssueTeamInvitationRequest: stable request_id, email, device_access_mode, device_ids. |
| POST | /api/v1/team/invitations/{id}/issue |
Same issuance contract with current version; recipient cannot change or device access expand. |
| POST | /api/v1/team/invitations/{id}/revoke |
TeamVersionRequest: current version. |
| GET | /api/v1/settings |
WorkspaceSettingsResponse: policy, retention, limit availability and version. |
| PUT | /api/v1/settings/retention |
SetAuditRetentionRequest: audit_retention_days, expected_audit_retention_days, current version. |
Issuance returns TeamInvitationDeliveryResponse: state, invitation metadata,
customer_origin advertised by the authenticated Bridge, and a one-time link
only for new delivery. The browser/customer origin can differ from a retained
native API origin. Keep the link in the required protected CLI output, never
stdout, model context or logs. Identical request replay returns status_only
with no link; changed payloads fail. Replacement/revoke invalidate old links and
pending acceptance handles. Mech uses server-rendered exact-change confirmation
and a human-only private download; its model cannot approve or read that link.
Target versions, actor/run/scope, expiry and the exact payload are rechecked in
the locked service transaction. Existing legacy request fields remain compatible;
new agent clients always send the current version. No mutation is automatically
retried after an ambiguous result.
Mech controller decisions
The required bearer decision adapter shares Mech's existing controller transition; its publication is recorded on the release page, not inferred from this reference. It does not create a conversation/run, let a model approve itself or grant a higher role. Only the signed-in current human controller may answer; the underlying operation retains its own live workspace/device/role and exact-change checks.
| Method | Route | Contract |
|---|---|---|
| GET | /api/v1/mech/conversations/{id}/decisions |
MechDecisionResponse: current pending server-rendered question/proposal or no_pending_decision. |
| GET | /api/v1/mech/conversations/{id}/decisions/{decision} |
The exact pending decision or durable approved/declined/consumed metadata; unavailable decisions are not inferred successful. |
| POST | /api/v1/mech/conversations/{id}/decisions/{decision} |
MechDecisionAnswerRequest: observed run_id, conversation version, explicit approved, exact payload_sha256; ordinary questions instead supply answer with null approval/digest. |
A confirmation requires an empty answer and matching run, version and digest. Edits, expiry, changed controller or lost authority refuse it. Approval is recorded once and consumed in the actual service transaction; it is not completed-execution proof. No private invitation URL, server-only proof or execution credential is returned. Responses are no-store; API requests use bearer authentication and do not borrow the web cookie. After an ambiguous submission, read the same decision and target state before retrying, rather than generating another approval.
Enrollment sharing
For authorized human device managers:
| Method | Route | Request → response |
|---|---|---|
| POST | /api/v1/enrollment-shares |
CreateEnrollmentShareRequest → CreatedEnrollmentShareResponse. |
| GET | /api/v1/enrollment-shares/{id} |
EnrollmentShareResponse. |
| DELETE | /api/v1/enrollment-shares/{id} |
Updated EnrollmentShareResponse. |
Creation input is label, platform win-x64/linux-x64 or Mac-preview osx-arm64/osx-x64, and optional
expires_at. Null expiry means 24 hours; the maximum is seven days.
Legacy unkeyed creation returns share and one-time capability url.
Current agent issuance uses POST /api/v1/enrollment-shares/issue with retained
idempotency_key and EnrollmentShareDeliveryResponse. Status-only replay need not
return a URL; never fall back to legacy create after uncertainty. Optional
support_access_mode/classification and exact replacement/intake/association
identity retain separate authority.
Never store that URL in an ordinary request log or operational receipt.
Status includes id, label/platform/release metadata, created_at,
expires_at, state, and optional downloaded_at, consumed_at, online_at
and enrolled_device_id. Status does not return a recoverable capability or
code. Consumption and authenticated online presence are distinct.
Use enrollment guidance for delivery and installation; native code redemption and package trust belong to the signed client, not a browser script.
History and the work map
| GET route | Query and response |
|---|---|
/api/v1/access-sessions |
Optional actor, device, state, since, until, cursor, limit; returns AccessSessionPage. State is active or ended. |
/api/v1/access-sessions/{id} |
Optional cursor, limit; returns AccessSessionDetail. |
/api/v1/access-activity/uncorrelated |
Optional device, cursor, limit; returns UncorrelatedActivityPage. |
/api/v1/temporary-devices |
Optional history, cursor, limit; returns TemporaryDevicePage. history=true selects ended/promoted history. |
/api/v1/temporary-devices/{id} |
TemporaryDeviceDetail, with separately authorized promotion information. |
/api/v1/overview |
Optional customer, case, technician, activity, cursor; returns OverviewSnapshot. Activity is all, active or idle. |
Session pages use items and next_cursor. Session detail instead has
session, events, next_cursor. Session listing defaults to 25 items and
detail to 50; both accept limits 1–100. since is inclusive and until
exclusive on the access period's start.
AccessSessionSummary contains id, actor_id, actor_name, target_kind,
target_id, target_name, started_at, last_observed_at, optional
ended_at, state, end_reason, client_version, host_version.
An AuditEventResponse includes event/actor/target identifiers, outcome,
timestamps, optional version/transfer-hash metadata and provenance.
These are metadata, not commands or file contents.
TemporaryDeviceDetail contains temporary, optional access_session_id,
optional promotion, cleanup_outcome, optional cleanup_observed_at,
and cleanup_provenance. A promotion's health_confirmed_at comes from the
authenticated permanent device; a cleanup observation does not certify
remote process removal. History access does not confer Rescue control.
OverviewSnapshot contains workspace_id, observed_at, policy_version,
customer_context_available, enrolled/online counts, nodes, edges,
groups, omitted_connections, next_cursor. Nodes identify company, case,
technician, device, session or Rescue records. Groups include authorized
total and expanded counts. The bounded map describes explicit
relationships and observed access, not agent jobs or repair progress.
Customer usage and Mech lifecycle
Current summary is v3; v1/v2 projections remain old-client compatibility. Mech settings include Plan; exact confirmed changes snapshot subsequent runs and cannot modify an active run or grant authority.
| Method | Route | Contract |
|---|---|---|
| GET | /api/v1/usage/ai |
Required UTC since/until (at most 31 days); optional conversation/run/controller/before, caseId/company/contact, groupBy/groupCursor/groupLimit, and independent Admin-only workSession; customer retail report. |
| GET | /api/v1/work-sessions |
Admin only; since, until, technician, cursor, limit; WorkSessionReportPage. |
| GET | /api/v1/work-sessions/current/v3 |
Admin-only frozen current-month retail estimate, same as Billing. |
| GET | /api/v1/work-sessions/{id}/events |
Admin only; chronological content-free events, cursor, limit. |
| GET | /api/v1/releases |
Signed global Host release metadata, never publication rights. |
| GET | /api/v1/mech/capabilities |
Permitted model/reasoning/scope selections, customer retail rates and attachment limits. |
| GET / POST | /api/v1/mech/conversations |
Paged read (archived, cursor, limit); create with stable request_id, optional title/model/reasoning, scope_mode, device_ids, case_id. |
| GET | /api/v1/mech/conversations/{id} |
Private MechConversationDetail: metadata, transcript and activity; proposals are read via the shared decision API. |
| POST | .../{id}/rename |
Current version, exact title. |
| POST | .../{id}/archive, /claim, /release |
Current version; idle and controller checks. Claim cannot take another controller. |
| POST | .../{id}/settings |
Current version, allowed model, reasoning. |
| POST | .../{id}/scope |
Current version, scope_mode, device_ids; never wider than a Mech caller's existing fence. |
| POST | .../{id}/context |
Current version, device_ids, nullable case_id; focus is not authority. |
| POST | .../{id}/messages |
Current version, stable client_message_id, text, optional owned attachment_ids; returns MechRunStart without holding an observer stream. |
| POST | .../{id}/continue |
Current version, exact prior run_id, stable client_message_id; explicit interrupted-work recovery. |
| POST | .../{id}/stop, /cancel |
Current version, exact run_id; fences only that run. |
| POST | .../{id}/guidance |
Exact run_id, stable client_message_id, bounded text; lost injection acknowledgement stays uncertain. |
| GET | .../{id}/runs, /runs/{run} |
Paged run identities/status; exact-run private transcript and operation results. |
| GET | .../{id}/events |
after_sequence, limit; bounded replay page. Private decision events are redacted; use the shared role-checked decision API. |
| GET | .../{id}/files, /files/{file}/download |
Bounded file metadata page (offset, limit) or integrity-checked private bytes; opaque IDs, no server paths. |
| POST | .../{id}/attachments |
Bounded multipart request_id and customer files, same classifier and private store as browser Send; stable staging before explicit message submission. |
... means /api/v1/mech/conversations. Page limits are 1–100. Create and
message keys are canonical UUIDs. created:false means no new run or provider
call; the response run_id identifies the original input run even if conversation
metadata now identifies a newer run. Every mutation rechecks the current controller, role, scope and revision
under the workspace lock; no client adopts another client's lease. Read status
and results after a timeout—never automatically replay a mutation. Private bodies
belong in protected local files, not stdout, Git or operational logs. Pending
questions/meaningful decisions use the existing
controller decision routes; a boolean or model
statement cannot grant additional authority.
Customer directory
In the table, {collection} is companies or contacts.
| Method | Route | Request → response |
|---|---|---|
| GET | /api/v1/{collection} |
search, archive, cursor, limit; contacts also accept company. Returns CompanyPage or ContactPage. |
| GET | /api/v1/{collection}/{id} |
CompanyDto or ContactDto, including archived records. |
| POST | /api/v1/{collection} |
CompanyWriteRequest or ContactWriteRequest → created DTO, HTTP 201. |
| PUT | /api/v1/{collection}/{id} |
Full write request with current version → updated DTO. |
| POST | /api/v1/{collection}/{id}/archive |
DirectoryVersionRequest → updated DTO. |
| POST | /api/v1/{collection}/{id}/restore |
DirectoryVersionRequest → updated DTO. |
| GET | /api/v1/devices/{id}/customer-links |
DeviceCustomerLinksDto. |
| PUT | /api/v1/devices/{id}/customer-links |
DeviceCustomerLinksRequest → updated links. |
Creation also accepts optional persisted idempotency_key.
Write fields are display_name (1–200 characters), optional email (one
address, up to 320), optional phone (up to 64), and version for updates.
Contacts add nullable company_id. Companies add nullable primary_contact_id:
omitted retains the recorded designation, explicit null clears, and a UUID selects
an active same-workspace contact already linked to that company. An unchanged
archived contact may be retained, not newly selected. Creation accepts only
omitted/null designation; link a contact after creating the company, then update
at its current version. Clear/change a company's designation before moving or
unlinking that contact. Selection never assigns contact membership or access.
Other omitted optional content fields still clear them during an update.
DirectoryVersionRequest is { "version": 1 }, with the actual current version
substituted.
Company/contact DTOs include the content plus id, archived_at, version,
created_at, updated_at. Contacts also return a display-only company
reference. A reference has id, display_name, archived_at.
Companies return primary_contact_id and primary_contact, both null when unset.
A recorded primary contact includes id, available, nullable display_name,
email, phone, archived_at. Availability requires the contact still belongs
to this company in the same workspace; archive is reported separately. A
missing/foreign/mismatched relation retains only the recorded ID with
available: false and null customer facts. No list ordering creates designation.
Directory responses advertise X-Forge-Company-Primary-Contact-Version: 1;
establish this capability before relying on a write, as older servers may ignore
an additive field. The native CLI refuses unsupported designation writes and
reconciles uncertain receipts rather than replaying them.
Device-link input must explicitly include both nullable company_id
and primary_contact_id, plus its independent version. Null unlinks.
The response includes device_id, the IDs, display-only company and
primary_contact references, and the link version.
Pages have items, next_cursor; limit is 1–100, default 25. Archive is
active (default), archived or all. Search is a bounded case-insensitive literal match over name, email, phone and authorized related records. Preserve the same filters when continuing a cursor.
See customers for full examples, preserved archived references and keyed-versus-unkeyed create/retry behavior.
Cases and narrative
All routes below start with /api/v1/cases. {id} is the case UUID.
| Method | Suffix | Request → response |
|---|---|---|
| GET | (none) | Optional search, status, company, contact, archive, one of device/session, cursor, limit → CasePage<CaseDto>. |
| POST | (none) | CaseCreateRequest → CaseDto, HTTP 201. |
| GET | /members |
cursor, limit → CasePage<CaseAuthorDto>; existing human collaborators, not account management. |
| GET / PUT | /{id} |
Read CaseDto / replace with CaseWriteRequest. |
| POST | /{id}/status |
CaseStatusRequest → CaseDto. |
| POST | /{id}/close, /reopen, /archive, /restore |
CaseVersionRequest → CaseDto. |
| GET / POST | /{id}/collaborators |
Array of CaseAssigneeDto / CaseAssigneeRequest → CaseDto. |
| DELETE | /{id}/collaborators/{member} |
Body CaseVersionRequest → CaseDto. |
| GET / POST | /{id}/links |
Array of CaseLinkDto / CaseLinkRequest → CaseDto. |
| DELETE | /{id}/links/{link} |
Body CaseVersionRequest → CaseDto. |
| GET / POST | /{id}/notes |
Paged CaseNoteDto / CaseNoteCreateRequest → CaseNoteDto, HTTP 201. |
| GET / PUT | /{id}/notes/{note} |
CaseNoteDto / CaseNoteWriteRequest → updated note. |
| GET | /{id}/notes/{note}/revisions |
Paged CaseNoteRevisionDto. |
| GET / POST | /{id}/drafts |
Paged CaseDraftDto / CaseDraftCreateRequest → CaseDraftDto, HTTP 201. |
| GET / PUT | /{id}/drafts/{draft} |
CaseDraftDto / CaseDraftWriteRequest → updated draft. |
Every lifecycle suffix above is relative to /{id}; for example, reopening
uses /api/v1/cases/{id}/reopen. Paged requests accept cursor and limit
(case discovery 1–100; member/note/draft/revision 1–50; default 25), returning items and next_cursor. Requests are limited
to 128 KiB and reject unknown author/review claims.
Request shapes
| DTO | Fields |
|---|---|
CaseCreateRequest |
Required title; optional idempotency_key, description, company_id, primary_contact_id, device_id, access_session_id. |
CaseWriteRequest |
Required version, title; replacement description, company_id, primary_contact_id. |
CaseVersionRequest |
Current version. |
CaseStatusRequest |
Current version, status: open, in_progress or waiting. |
CaseAssigneeRequest |
Current case version, user_id. |
CaseLinkRequest |
Current case version, kind: device or session, and target_id. |
CaseNoteCreateRequest |
Required idempotency_key, body; kind defaults to finding, basis to observation; optional source, agent_name, references. |
CaseNoteWriteRequest |
Current note version, replacement note fields; no idempotency key. |
CaseDraftCreateRequest |
recipient_address, subject, body; optional idempotency_key, recipient_contact_id. |
CaseDraftWriteRequest |
Current draft version plus replacement draft fields. Every update clears review. |
Case titles allow 200 characters and descriptions 8,000. Note/draft bodies
allow 16,000; draft subjects 300 and recipient addresses 320.
Notes accept kinds finding, action, result, next_step and bases
observation, inference. Each of at most ten references has kind
(device, session, authorized audit) and target_id.
Response and review contracts
CaseDtoincludes metadata, customer references,creator, timestamps,status,closed_at,archived_at,version.CaseAuthorDtocontainsid,display_name,former_member.CaseLinkDtocontains its own unlinkableid,evidence,created_at. Unavailable evidence does not expose a target ID or label.CaseNoteDtocontainsid,creator,created_at,current.currentis an immutable attributed revision containingversion, content, basis/source/agent labels,author,recorded_at,references.CaseDraftDtocontains recipient/contact, subject/body, its own version, author/timestamps,state,reviewed_version,reviewer,reviewed_at. It is a draft, never a delivery receipt.
Mech review_draft renders the exact recipient/subject/body/revision and waits
for genuine current-controller approval, consuming its bound decision in the
same locked case service transaction. Review uses current CasesManage, not
Admin-only team permission. The existing website review remains cookie/
antiforgery-protected. Case drafts have no send API; customer messages below are a
separate, separately gated capability. These additions require compatible
Bridge/CLI publication; source alone is not a released-capability claim.
See the case workflow.
Customer messages and customer-safe case links
Work item: opalspan#1019.
Workspace-to-customer case updates (started, update, finished) with exact preview,
human approval and one-shot dispatch. Real delivery is separately gated and
off by default; availability.can_prepare and availability.can_deliver
report independently with stable reasons. A gated-off send is a known refusal
(503 customer_mail_unavailable) before dispatch: the message stays prepared
and its link stays unpublished. Contracts: CustomerCommunicationContracts.cs.
| Method | Route | Purpose |
|---|---|---|
| GET / PUT | /api/v1/customer-communications/settings |
Human case readers read brand, verified reply-to, actual platform sender and availability; PUT is workspace Admin only. |
| GET | /api/v1/customer-communications/instruction |
Human case readers see the selected publication and applicable attributed workspace instruction; publication is unselected by default. |
| POST | /api/v1/customer-communications/instruction/reviews |
Admin freezes a protected review of the exact underlying agreement, supplemental documents, settings revision and SES provider/region/retention binding. Its digest identifies content, never acceptance. |
| GET | /browser/v1/customer-communications/instruction/reviews/{id} |
Same signed-in human Admin reads the original protected review after navigation or reload; no creation, acceptance or state mutation. |
| POST | /browser/v1/customer-communications/instruction/reviews/{id}/decision |
Cookie-authenticated same human Admin accepts or declines the review; antiforgery and fresh actor/version/binding checks. No bearer/CLI decision route. |
| GET | /api/v1/customer-communications/settings/reply-to-candidates |
Admin-only members with current verified sign-in address evidence. |
| GET / POST | /api/v1/customer-communications/suppressions, /{id}/lift |
Admin view; only human-reported returned suppressions can be lifted, never a recipient opt-out. |
| GET / POST | /api/v1/cases/{id}/customer-messages, /prepare |
Bounded list; freeze one exact message for an explicit case_customer, device_owner or selected_responsible_contact recipient. Nothing is sent or published. |
| GET | /api/v1/cases/{id}/customer-messages/{message} |
Message, frozen HTML with capability fragments redacted, digests, customer-page projection and delivery state. |
| POST | /api/v1/cases/{id}/customer-messages/{message}/approvals, /approvals/{approval} (GET) |
Request and read a one-use approval for the same signed-in human; returns human_review_url. A digest is content identity, not consent. |
| POST | /api/v1/cases/{id}/customer-messages/{message}/send |
Consume that human's website-approved approval in admission, then dispatch once. Repeats return the stored receipt. |
| POST | /api/v1/cases/{id}/customer-messages/{message}/cancel, /returned |
Cancel a prepared message; report human-observed returned mail (workspace-scoped suppression). |
| GET / POST | /api/v1/cases/{id}/customer-links, /{link}/revoke |
Link state and revocation. Links exist only through a message. |
| POST | /customer-case/v1/view, /preferences/status, /preferences/stop |
Anonymous, no-store/noindex/no-referrer, same-origin JSON with the fragment token in the body. Invalid, expired and revoked links share 404 link_unavailable; preferences are opt-out only for one case and never return case content. |
The /browser/v1/... adapter adds cookie-only /approvals/{approval}/decision
and /confirm; only there does a prepared preview contain live capability
fragments. Provider acceptance is never inbox delivery, and bounces are not
observed automatically. Generic provider, configuration or rate-limit failures
never suppress a recipient.
Customer mail uses the maintained SESv2 SDK in eu-north-1, with automatic request
retries and SDK logging disabled. The fixed platform From, identity ARN and named
configuration set are frozen with the exact email; ordinary replies use the
workspace member's verified Reply-To. No contact-list or tracking API is used.
The customer transport is separate from Microsoft member/billing mail and Auth0
sending. Its dedicated protected credential file and explicit profile never use
the operator/default credential chain. Every delivery, runtime and publication
switch defaults off; sender, identity, credentials, configuration-set and document
bindings default empty. A verified domain alone does not enable customer sending.
Private preparation remains available while delivery is unavailable.
workspace_instruction_missing means no current workspace instruction matches the
reviewed settings, actor and published documents. The instruction's acceptance
records UTC time, identity/version, request, underlying agreement, three supplement
IDs/hashes, provider, region and retention binding; it never supplies approval for
an individual send. SES acceptance includes its provider message ID but proves
neither inbox arrival nor observed recipient feedback.
Errors and retries
Common status meanings:
| HTTP status | Action |
|---|---|
| 400 | Correct malformed input, unsupported selection or invalid relationship. |
| 401 / 403 | Reauthenticate or verify current membership/feature authority; do not retry under an unrelated identity. |
| 404 | The record is unavailable under current scope; it may be missing or inaccessible. |
| 409 | Reload the current version or resolve lifecycle/idempotency conflict. |
| 413 | Reduce the bounded request body. |
| 429 | Respect throttling; slow down rather than repeating immediately. |
| 503 | Service or verified release unavailable; inspect state before retrying a write. |
Many application failures return { "error": "<code>", "message": "<detail>" }.
Overview has a separate code error field; authorization/routing failures
may not return an application JSON body. Do not assume one universal error
envelope.
Important feature codes include directory_not_found, directory_conflict,
invalid_case, case_conflict, case_archived and case_storage_unavailable.
Note creation requires its original idempotency key. Company/contact/case/draft
creates support capability-gated optional persisted keys; unkeyed creates remain
non-idempotent. Replay preserves actor/workspace/parent and unchanged payload/current
content. Never remove a key or change payload after an uncertain keyed mutation.
Device-work accounting
GET /api/v1/device-work/{operation-id} reads the current actor's content-free
native lifecycle and distinct commercial references. It requires current selected
workspace membership; another user's identifier grants no access. It returns
operation_id, state, new_session_ids, reused_session_ids,
platform_covered_session_ids, new_sessions and reused_sessions.
Reservations are not usage. Missing or uncertain evidence never means continued
execution or completed repair and is not permission to replay a command.
Only the authenticated native Host may report device acceptance, continuity and completion. A signed-in agent cannot manufacture usage by submitting command text, filenames, traffic, an inference decision or an untrusted lifecycle assertion. Mech's server-owned run references share the same unit rather than create charges.