Developers
Kreto API reference
Classify notices, check payroll, work responses and more. Every operation below is generated from one OpenAPI specification. The Kreto SDK, CLI and MCP server are generated from the same spec; they are not published yet.
What firms build with it.
Bring notices in from your own intake.
The Notices operations, starting with /api/notices/upload.
Push payroll registers after each run.
The Payroll operations, starting with /api/payroll/upload.
Pull each client's deadlines and statuses into your own dashboard.
The Compliance operations, starting with /api/compliance-calendar.
Authentication
UI clients sign in with a session and need no setup. Integrations use a Bearer token scoped to your firm. Create and manage keys in Settings, API access. Each request is isolated to the key’s firm by row-level security, and each key carries a list of scopes: an operation runs only if the key holds every scope it lists.
Available scopes (18). Grant a key only the scopes its integration needs.
admin.readadmin.writeannual_reports.readannual_reports.writeaudit.readaudit.writeemployers.reademployers.writeforeign_qualifications.readforeign_qualifications.writehandbooks.readhandbooks.writenotices.readnotices.writepayroll.readpayroll.writestate_registration.readstate_registration.writeAgency documents7
GET/api/agency-documentsList an employer's agency documents
One row per agency document (its newest reading). The accountant sees each figure with its page and quote; the employer sees the status word only. Bearer scope: `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entityId | query | string | required | — |
Responses
- 200The employer's agency documents
AgencyDocumentListField Type Required Description agency_documentsarray<AgencyDocument>required — - 401Unauthorized
MiscUnauthorizedResponse - 404Not found — an absent, foreign or out-of-scope id (or the feature off) answers the same body
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithDetails · AccountSetupIncompleteResponse)
POST/api/agency-documentsAdd an agency document
Add a state agency's rate notice, benefit-charge statement or other agency letter for an employer (multipart: `file`, `entity_id` — or a `notice_id` alone, its employer then — `kind` `rate_notice` | `benefit_charge_statement` | `other_agency_letter`). The accountant adds one for any employer it serves; the employer's own user (or key) for its own employer only, and is never shown a figure — the reading waits for the accountant's confirm. PDF, PNG, JPEG or TIFF under 25 MB, checked against the bytes. The same file for the same employer is one document (`deduplicated: true`). Kreto reads it in the background; nothing is used until an accountant confirms the figures. Bearer scope: `notices.write`.
notices.writeRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
file | string · binary | required | — |
entity_id | string | optional | — |
notice_id | string | optional | — |
kind | enum: "rate_notice" | "benefit_charge_statement" | "other_agency_letter" | required | — |
Responses
- 202Added; the reading is queued
AgencyDocumentAddedField Type Required Description document_idstringrequired — deduplicatedbooleanrequired — job_idstringrequired — - 400`invalid_upload` — no file, or an unknown kind
AgencyDocumentError - 401Unauthorized
MiscUnauthorizedResponse - 403`CPA_ROLE_REQUIRED` — a role that is neither the accountant nor the employer's own user
AgencyDocumentError - 404Not found — an absent, foreign or out-of-scope id (or the feature off) answers the same body
MiscNotFoundResponse - 413`file_too_large`
AgencyDocumentError - 415`unsupported_type` — not a PDF/PNG/JPEG/TIFF by its bytes
AgencyDocumentError - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithDetails · AccountSetupIncompleteResponse)
GET/api/agency-documents/{id}Read one agency-document reading
One reading, projected for the caller (figures for the accountant, the status word for the employer). Bearer scope: `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | — |
Responses
- 200The reading
AgencyDocumentOneField Type Required Description agency_documentAgencyDocumentrequired — - 401Unauthorized
MiscUnauthorizedResponse - 404Not found — an absent, foreign or out-of-scope id (or the feature off) answers the same body
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithDetails · AccountSetupIncompleteResponse)
POST/api/agency-documents/{id}/confirmConfirm an agency document's figures
The accountant confirms the figures Kreto read (rejecting any misread ones, or correcting one to the value printed on the page — Kreto's reading and quote are kept beside the correction). They are applied to the notice they are usable for — only when the document is dated the state's computation date for that rate year — as the agency's own figures; a figure that differs from one the employer stated is held for review. Bearer scope: `notices.write` (a firm key).
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | — |
Request body AgencyConfirmBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
notice_id | string | optional | — |
reject | array<object> | optional | — |
edit | array<object> | optional | — |
Responses
- 200Confirmed; each candidate notice's outcome
AgencyConfirmedField Type Required Description confirmedenum: truerequired — extraction_idstringrequired — acceptedarray<string>required — rejectedarray<string>required — editedarray<string>required — projectionsarray<object>required — - 400`figure_invalid` — every figure was rejected; a correction names a figure Kreto did not read, a rejected figure, or a negative amount; or a kept figure's quote was not found on the page and no value was typed for it
AgencyDocumentError - 401Unauthorized
MiscUnauthorizedResponse - 403`CPA_ROLE_REQUIRED` — only the accountant confirms
AgencyDocumentError - 404Not found — an absent, foreign or out-of-scope id (or the feature off) answers the same body
MiscNotFoundResponse - 409`already_confirmed` | `extraction_not_ready`
AgencyDocumentError - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithDetails · AccountSetupIncompleteResponse)
GET/api/agency-documents/{id}/fileOpen an agency document's stored file
A 5-minute signed link to the stored file, so the accountant's confirm card can open it at the page a figure's quote was read from. Accountant session only: an API key never mints a file link, and the employer never gets one (the employer is never shown a figure).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | — |
Responses
- 200A short-lived link to the file
AgencyDocumentFileField Type Required Description urlstringrequired — mime_typestringrequired — expires_in_secondsintegerrequired — - 401Unauthorized
MiscUnauthorizedResponse - 404Not found — an absent, foreign or out-of-scope id (or the feature off) answers the same body
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithDetails · AccountSetupIncompleteResponse)
POST/api/agency-documents/{id}/rejectReject one figure Kreto read
Before confirming, the accountant rejects one figure with a reason from a closed list (`misread_value`, `wrong_line`, `wrong_period`, `not_on_document`, `illegible`, `other` — `other` needs a note). Kreto's reading and quote are kept; the figure is never applied to a notice, and re-opens only when the accountant types the value printed on the page in the confirm. Bearer scope: `notices.write` (a firm key).
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | — |
Request body AgencyRejectBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
key | string | required | — |
reason | enum: "misread_value" | "wrong_line" | "wrong_period" | "not_on_document" | … | required | — |
note | string | optional | — |
notice_id | string | optional | — |
Responses
- 200Rejected
AgencyRejectedField Type Required Description rejectedenum: truerequired — extraction_idstringrequired — keystringrequired — reasonenum: "misread_value" | "wrong_line" | "wrong_period" | "not_on_document" | …required — - 400`figure_invalid` — no reason (or one off the list), `other` without a note, a figure Kreto did not read, or one already rejected
AgencyDocumentError - 401Unauthorized
MiscUnauthorizedResponse - 403`CPA_ROLE_REQUIRED` — only the accountant rejects
AgencyDocumentError - 404Not found — an absent, foreign or out-of-scope id (or the feature off) answers the same body
MiscNotFoundResponse - 409`already_confirmed` | `extraction_not_ready` | `reading_changed`
AgencyDocumentError - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithDetails · AccountSetupIncompleteResponse)
POST/api/agency-documents/{id}/accept-allAccept every figure in one step
Confirms every figure of the reading at once — only when every figure was read with high confidence, its quote was found on the page, none was rejected and the reader's quote and date checks passed (the reading's `bulk_accept_available`). Otherwise refused as `bulk_requires_all_high`, naming each reason in `blocks` (the same sentences as the reading's `bulk_accept_blocks`); confirm figure by figure instead. Applied exactly like a confirm, with one audit entry per figure. Bearer scope: `notices.write` (a firm key).
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | — |
Request body AgencyAcceptAllBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
notice_id | string | optional | — |
Responses
- 200Confirmed; each candidate notice's outcome
AgencyConfirmedField Type Required Description confirmedenum: truerequired — extraction_idstringrequired — acceptedarray<string>required — rejectedarray<string>required — editedarray<string>required — projectionsarray<object>required — - 401Unauthorized
MiscUnauthorizedResponse - 403`CPA_ROLE_REQUIRED` — only the accountant confirms
AgencyDocumentError - 404Not found — an absent, foreign or out-of-scope id (or the feature off) answers the same body
MiscNotFoundResponse - 409`bulk_requires_all_high` (with `blocks`: each reason named) | `already_confirmed` | `extraction_not_ready`
one of (AgencyBulkAcceptRefused · AgencyDocumentError) - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithDetails · AccountSetupIncompleteResponse)
Agency portal downloads2
GET/api/portal-fetchList agency-portal downloads
The agency-portal downloads Kreto ran for one stored login (`credential_id`) or one employer (`entity_id`) — name exactly one — newest first, with the status word and, for the accountant, the next step. No files on a list row (read one run for those). Bearer scope: `admin.read` (a dedicated `portal_fetch.read` scope is owed).
admin.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
credential_id | query | string | optional | — |
entity_id | query | string | optional | — |
Responses
- 200The runs, newest first
PortalFetchListField Type Required Description planeenum: "cpa" | "employer"required — credentialobjectrequired — runsarray<PortalFetchRun>required — - 400`INVALID_INPUT` — neither or both ids named
PortalFetchError - 401Unauthorized
MiscUnauthorizedResponse - 403A cookie role that is not the accountant's, or a Bearer key without `admin.read`
PortalFetchError - 404Not found — an absent, foreign or out-of-scope id answers the same body
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithDetails · AccountSetupIncompleteResponse)
GET/api/portal-fetch/{id}Read one agency-portal download
One run and its files: the status word, the next step for the accountant, and per file its kind, date and where Kreto put it. The employer sees dates and kinds only. Bearer scope: `admin.read` (a dedicated `portal_fetch.read` scope is owed).
admin.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | — |
Responses
- 200The run and its files
PortalFetchOneField Type Required Description planeenum: "cpa" | "employer"required — runPortalFetchRunrequired — - 401Unauthorized
MiscUnauthorizedResponse - 403A cookie role that is not the accountant's, or a Bearer key without `admin.read`
PortalFetchError - 404Not found — an absent, foreign or out-of-scope id answers the same body
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithDetails · AccountSetupIncompleteResponse)
Analytics1
GET/api/analytics/predictionsEntity risk predictions
Risk predictions for an entity: SUI rate-change forecast, ACA penalty risk (when FTE >= 40), and overall risk profile aggregating open/overdue notices + active state count. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | required | — |
Responses
- 200Risk predictions
AnalyticsPredictionsResponseField Type Required Description entityobjectrequired — riskProfileobjectrequired — suiPredictionobjectrequired — acaRiskobjectrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA access required
MiscUnauthorizedResponse - 404Entity not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
API Keys4
GET/api/api-keysList API keys for the caller's tenant
Returns all API keys (active + revoked) for the authenticated CPA's tenant. Response NEVER includes plaintext or key_hash — only the publicly safe key_prefix + metadata.
Responses
- 200List of API keys
ListApiKeysResponseField Type Required Description keysarray<PublicApiKey>required — - 401Unauthorized
ApiKeysNotFoundResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth gate's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/api-keysIssue a new API key
Create a new API key for the authenticated CPA's tenant. Plaintext is returned ONCE in the response (`plaintext` field) — store it securely; subsequent GET calls return only the masked metadata. `scopes` is REQUIRED and must name at least one scope; an omitted, null or empty array is refused 400 with code `SCOPES_REQUIRED` — a key is never issued with every scope by default, and a key with no scopes authorizes nothing.
Request body CreateApiKeyBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
name | string | required | — |
scopes | array<enum: "notices.read" | "employers.read" | "payroll.read" | "audit.read" | …> | required | REQUIRED, non-empty scope array (inbox 232). Omitted, null or [] is refused 400 SCOPES_REQUIRED — the server never defaults a key to every scope. A stored key with no scopes authorizes nothing. |
expires_at | string · date-time | optional | Optional expiry. Omitted or NULL = 90 days from mint (DEFAULT_TOKEN_LIFETIME_DAYS); a key never mints without one. |
Responses
- 201Key issued. Plaintext returned ONCE; store it now — it cannot be retrieved again.
IssuedApiKeyField Type Required Description idstringrequired — tenant_idstringrequired — key_prefixstringrequired — namestringrequired — scopesarray<enum: "notices.read" | "employers.read" | "payroll.read" | "audit.read" | …>required — created_by_user_idstringrequired — created_atstring · date-timerequired — last_used_atstring · date-timerequired — revoked_atstring · date-timerequired — expires_atstring · date-timerequired — plaintextstringrequired — warningenum: "This is the ONLY time the plaintext is shown. Store it securely now."required — - 400Validation error (Zod issues array in body), or `SCOPES_REQUIRED` when no scopes were requested
ApiKeysValidationErrorResponse - 401Unauthorized — caller is not authenticated as a CPA
ApiKeysNotFoundResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth gate's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/api-keys/{id}Fetch a single API key's metadata
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | — |
Responses
- 200Key metadata
objectField Type Required Description keyPublicApiKeyrequired — - 404Key not found in your tenant
ApiKeysNotFoundResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth gate's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
DELETE/api/api-keys/{id}Revoke an API key
Soft-delete via `revoked_at`. Idempotent — re-revoking returns the existing revocation timestamp. Optional body { reason: string } for audit context.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | — |
Request body object
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | optional | — |
Responses
- 200Key revoked (or already revoked — idempotent)
RevokeApiKeyResponseField Type Required Description revokedbooleanrequired — already_revokedbooleanrequired — revoked_atstring · date-timeoptional — keyPublicApiKeyoptional — - 404Key not found in your tenant
ApiKeysNotFoundResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth gate's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Audit1
GET/api/auditList audit events
Fetch tenant audit events, optionally filtered by entity_id. CPA-class roles only on cookie sessions; Bearer keys gate via `audit.read`.
audit.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | optional | — |
entity_id | query | string · uuid | optional | — |
Responses
- 200Audit events
AuditEventsResponseField Type Required Description okenum: truerequired — eventsarray<AuditEvent>required — totalintegerrequired — - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA access required
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Auth1
POST/api/auth/mfa/unenrollRemove one of the caller's MFA factors (step-up required)
Cookie session only; a Bearer API key is refused. The SESSION must be at assurance level aal2 (a TOTP challenge verified on this session) — otherwise 403 `STEP_UP_REQUIRED` and nothing is removed; complete the challenge and retry. The factor must be one of the session user's own factors; the body's `factorId` is the only field read (a user id in the body is ignored). A successful removal writes one `mfa_factor_unenrolled` audit event before the 200.
Request body MfaUnenrollRequestrequired
| Field | Type | Required | Description |
|---|---|---|---|
factorId | string · uuid | required | — |
Responses
- 200Factor removed
MfaUnenrollResponseField Type Required Description okenum: truerequired — factor_idstring · uuidrequired — - 400INVALID_BODY — body is not JSON or `factorId` is not a factor id
MfaUnenrollError - 401No session (or a Bearer key was presented)
MfaUnenrollError - 403STEP_UP_REQUIRED — the session is not aal2; or `mfa_required` from the cookie-API MFA gate
MfaUnenrollError - 404FACTOR_NOT_FOUND — not one of the session user's factors
MfaUnenrollError - 500Server error — sanitized (`SanitizedServerError`), or a signed-in session with no profile gets `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Compliance22
POST/api/compliance/digestTrigger compliance digest
Manually trigger a weekly compliance digest email, or (body `{"mode":"preview"}`) build it without sending. Designed send refusals are returned in `skipped`, real failures in `errors`. CPA-class roles only on cookie sessions; Bearer keys gate via `admin.write` scope — sending mail on the tenant's behalf is a side effect, and the route ENFORCES admin.write (route.ts). SWEEP C16: the declaration said `admin.read` and was the half that was wrong.
admin.writeRequest body ComplianceDigestRequest
| Field | Type | Required | Description |
|---|---|---|---|
mode | enum: "send" | "preview" | optional | `send` (default when the body is omitted) emails the digest to the firm's CPA members and admins whose weekly digest is on. `preview` builds and returns the rendered email without sending anything. |
Responses
- 200Digest sent (mode send) or built (mode preview)
ComplianceDigestResponse - 400Invalid body
ComplianceValidationErrorResponse - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — CPA access required / missing scope
ComplianceUnauthorizedResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/compliance/score-historyGet compliance score history
Returns the most recent 12 score-history entries for the caller's tenant (or for a specific entity when entity_id is supplied). The series holds only scores recorded by the compliance monitor and starts at 2026-10-01T00:00:00Z; entries from the earlier scoring method are not returned, so a caller with no monitor-recorded score yet receives an empty history.
audit.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | optional | — |
Responses
- 200Score history
ComplianceScoreHistoryResponseField Type Required Description historyarray<ComplianceScoreHistoryEntry>required — - 400Validation error
ComplianceValidationErrorResponse - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — missing required scope
ComplianceUnauthorizedResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/compliance/roi-summaryGet YTD ROI summary
Returns YTD savings, penalties avoided, time saved, and notice count derived from `roi_events` for the caller's tenant.
audit.readResponses
- 200YTD ROI summary
ComplianceRoiSummaryResponseField Type Required Description ytd_savingsintegerrequired — ytd_penalties_avoidedintegerrequired — ytd_time_saved_hoursintegerrequired — ytd_notices_processedintegerrequired — - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — missing required scope
ComplianceUnauthorizedResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/compliance/intelligence-scoreGet compliance intelligence score
Returns the Compliance Intelligence Score either for a single entity (when entity_id is supplied) or aggregated across all of the caller's tenant's entities.
audit.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | optional | — |
Responses
- 200Score (single or aggregate)
one of (ComplianceSingleEntityScoreResponse · ComplianceIntelligenceScore) - 400Validation error
ComplianceValidationErrorResponse - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — missing required scope
ComplianceUnauthorizedResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/compliance-score/{entityId}Get single-entity score (legacy)
Legacy path: returns the Compliance Intelligence Score for a given entity. Tenant-scoped on the entity verification (KRT-1479 P4e.1 Senior-Dev-Override fix).
audit.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entityId | path | string · uuid | required | Employer entity UUID |
Responses
- 200Compliance intelligence score
object - 400Validation error
ComplianceValidationErrorResponse - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — missing required scope
ComplianceUnauthorizedResponse - 404Entity not found in caller's tenant
ComplianceNotFoundResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/compliance/aca-summaryGet ACA summary
Returns ACA (Applicable Large Employer) status + penalty exposure per entity. Primary FTE source: payroll_pay_periods; fallback chain through payroll_records → onboarding range → entity.employee_count.
audit.readResponses
- 200ACA summary by entity
ComplianceAcaSummaryResponseField Type Required Description employeesintegerrequired — aleStatusenum: "applicable" | "not_applicable"required — dataQualityenum: "payroll" | "estimate"required — entitiesarray<ComplianceAcaEntity>required — - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — missing required scope
ComplianceUnauthorizedResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/compliance/benefitsList benefits compliance
List benefits compliance records for all tenant entities for a given plan year (defaults to current year).
audit.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
year | query | string | optional | Plan year |
Responses
- 200Benefits list
ComplianceBenefitsListResponseField Type Required Description recordsarray<object>required — yearintegerrequired — - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — missing required scope
ComplianceUnauthorizedResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/compliance/benefitsUpsert benefits compliance record
Upsert a benefits compliance record for an entity + plan year. Tenant-scoped on the entity verification (already correct pre-Phase-4).
notices.writeRequest body ComplianceBenefitsPostBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
plan_year | integer | required | — |
plan_type | string | optional | — |
carrier_name | string | optional | — |
plan_participants | integer | optional | — |
rxdc_completed | boolean | optional | — |
rxdc_completed_date | string · date | optional | — |
gag_clause_filed | boolean | optional | — |
gag_clause_filed_date | string · date | optional | — |
gag_clause_confirmation | string | optional | — |
medicare_part_d_sent | boolean | optional | — |
medicare_part_d_sent_date | string · date | optional | — |
chip_notice_sent | boolean | optional | — |
chip_notice_sent_date | string · date | optional | — |
form_5500_required | boolean | optional | — |
form_5500_filed | boolean | optional | — |
form_5500_filed_date | string · date | optional | — |
cobra_documented | boolean | optional | — |
Responses
- 200Record upserted
objectField Type Required Description recordBenefitsRecordrequired — - 400Validation error
ComplianceValidationErrorResponse - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — missing required scope
ComplianceUnauthorizedResponse - 404Entity not found in caller's tenant
ComplianceNotFoundResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/compliance/pfml-checkCheck PFML compliance gaps
PFML enrollment gap analysis for tenant entities. client_user role (cookie session) scoped to assigned entities; CPA-class + Bearer keys see all tenant entities.
audit.readResponses
- 200PFML gap analysis
CompliancePfmlResponseField Type Required Description entitiesarray<CompliancePfmlResult>required — unavailableEntitiesarray<CompliancePfmlUnavailable>required — summaryobjectrequired — - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — missing required scope
ComplianceUnauthorizedResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse) - 503The firm's entity list could not be read (no partial answer is served)
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/compliance/wage-hour-riskGet wage & hour risk scores
Returns wage & hour risk scores per entity (state minimum wage check, overtime policy gaps, exempt-employee threshold, contractor risk).
audit.readResponses
- 200Risk scores per entity
ComplianceWageHourResponseField Type Required Description entitiesarray<ComplianceWageHourEntity>required — federalMinimumWagenumberrequired — overtimeSalaryThresholdnumberrequired — overtimeSalaryThresholdSourceobjectoptional — errorstringoptional — - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — missing required scope
ComplianceUnauthorizedResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/compliance-monitorGet compliance monitor profiles
Returns compliance profiles for all employers (sorted worst-first). client_user role scoped to assigned entities; CPA-class + Bearer keys see all tenant entities.
audit.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | optional | Filter to a single entity (must be authorized) |
Responses
- 200Compliance profiles
ComplianceMonitorResponseField Type Required Description profilesarray<object>required — - 400Validation error
ComplianceValidationErrorResponse - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — entity access denied / missing scope
ComplianceUnauthorizedResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PATCH/api/compliance/credential-alerts/{id}/acknowledgeAcknowledge credential-refresh alert
Dismiss a credential_refresh_alerts row (KRT-1288). 404 covers not-found / already-acknowledged / auto-resolved equally.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Alert UUID |
Responses
- 200Acknowledged
ComplianceCredentialAlertsAcknowledgeResponseField Type Required Description alertobjectrequired — - 400Validation error
ComplianceValidationErrorResponse - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — CPA access required / missing scope
ComplianceUnauthorizedResponse - 404Alert not found or already handled
ComplianceNotFoundResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PATCH/api/compliance/recommendations/{id}/acknowledgeAcknowledge state-registration recommendation
Dismiss a state_registration_recommendations row (KRT-1216 PR-D).
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Recommendation UUID |
Responses
- 200Acknowledged
ComplianceRecommendationsAcknowledgeResponseField Type Required Description recommendationobjectrequired — - 400Validation error
ComplianceValidationErrorResponse - 401Unauthorized
ComplianceUnauthorizedResponse - 403A valid Bearer key without `notices.write`, a session that must complete an MFA step-up (`mfa_required`), or `role_forbidden` — a non-CPA cookie role (before any read), or a write refused on a row the caller can read
ComplianceRecommendationsAcknowledgeForbiddenResponse - 404No such recommendation in the caller's tenant (unknown or cross-tenant id — existence-opaque). The body keeps the route's historical marker text; an already-acknowledged row the caller can read is the 409, never this 404
ComplianceNotFoundResponse - 409Already acknowledged (`already_acknowledged`) — before the write, or the write lost a race to another acknowledger
ComplianceRecommendationsAcknowledgeConflictResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/compliance/payroll-tax-analysisGet payroll-tax analysis
Calculate expected federal payroll taxes (SS, Medicare, FUTA, 941, 940) per entity from the firm's uploaded pay-period payroll, else payroll_records, else none (dataSource=estimate, zero employees). Per-employee detail is stripped from response (PII).
audit.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | optional | — |
tax_year | query | integer | optional | Analysis year (default 2026). Must have a cited Social Security wage base, else 400. |
Responses
- 200Payroll-tax analysis
CompliancePayrollTaxAnalysisResponseField Type Required Description entitiesarray<CompliancePayrollTaxEntity>required — summaryobjectrequired — - 400Validation error
ComplianceValidationErrorResponse - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — missing required scope
ComplianceUnauthorizedResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithCode · AccountSetupIncompleteResponse)
POST/api/compliance/payroll-tax-analysisAnalyze payroll-tax compliance
Compare expected payroll-tax liability against quarterly deposit input. Returns expected + compliance comparison + dataSource. Per-employee detail is stripped from response (PII).
audit.readRequest body CompliancePayrollTaxAnalysisPostBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
tax_year | integer | optional | Analysis year (default 2026). Must have a cited Social Security wage base, else 400. |
deposits | array<object> | required | — |
Responses
- 200Comparison result
CompliancePayrollTaxAnalysisPostResponseField Type Required Description expectedobjectrequired — complianceobjectrequired — taxYearintegerrequired — dataSourceenum: "finch" | "upload" | "payroll" | "estimate"required — uploadobjectrequired Uploaded pay rows counted and the first/last effective date they cover; null unless dataSource is upload. dataSourceNotestringrequired Plain-language note on where the wages came from, for the CPA. - 400Validation error
ComplianceValidationErrorResponse - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — missing required scope
ComplianceUnauthorizedResponse - 404Entity not found in caller's tenant
ComplianceNotFoundResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithCode · AccountSetupIncompleteResponse)
GET/api/compliance/alertsGet unified compliance alerts
Returns a unified compliance-alerts list across 5 sources (compliance_checks, urgent + overdue + needs-review notices, state_registration_recommendations, credential_refresh_alerts).
audit.readResponses
- 200Compliance alerts
ComplianceAlertsResponseField Type Required Description alertsarray<ComplianceAlert>required — - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — missing required scope
ComplianceUnauthorizedResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/compliance-calendarGet compliance calendar
Returns notices + work_items + active licence renewals with deadlines, grouped into overdue / thisWeek / thisMonth / future buckets. Tenant-scoped (P4e.3 Senior-Dev-Override fix).
audit.readResponses
- 200Grouped deadline calendar
ComplianceCalendarResponseField Type Required Description overduearray<ComplianceCalendarItem>required — thisWeekarray<ComplianceCalendarItem>required — thisMontharray<ComplianceCalendarItem>required — futurearray<ComplianceCalendarItem>required — windowobjectrequired The window this surface answers from (G6 R1 A7). Unbounded: every open notice with a deadline, overdue included. `buckets` are the bucket boundaries inside that window, not a filter on it. summaryobjectrequired — - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — missing required scope
ComplianceUnauthorizedResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/calendar/tokenList calendar subscriptions
Lists the caller's ACTIVE iCal subscriptions (not revoked, not expired). Never returns `token_hash` or anything the feed URL could be reconstructed from — the plaintext token is shown once at mint time and the row id is the only handle afterwards. Tenant- and user-scoped.
admin.writeResponses
- 200Active subscriptions for the caller
CalendarTokenSummaryResponseField Type Required Description tokensarray<object>required — limitintegerrequired — - 401Unauthorized — no session, or the key's creator is gone
ComplianceUnauthorizedResponse - 403Forbidden — CPA-class access required / missing scope
ComplianceUnauthorizedResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
DELETE/api/calendar/tokenRevoke a calendar subscription
Revokes one iCal subscription (soft delete via `revoked_at`, appended to the hash-chained audit trail). The feed resolves tokens with `revoked_at IS NULL`, so this is what actually withdraws the capability, and it frees a slot against the active-token cap. 404 deliberately covers `not yours` / `no such id` / `already revoked` alike so ids are not enumerable.
admin.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | query | string · uuid | required | Subscription row id to revoke. |
Responses
- 200Subscription revoked
CalendarTokenRevokeResponseField Type Required Description okenum: truerequired — revoked_idstring · uuidrequired — - 400Missing or malformed (non-UUID) subscription id
ComplianceValidationErrorResponse - 401Unauthorized — no session, or the key's creator is gone
ComplianceUnauthorizedResponse - 403Forbidden — CPA-class access required / missing scope
ComplianceUnauthorizedResponse - 404Subscription not found or already revoked
ComplianceNotFoundResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/irs-correspondenceList IRS correspondence
Tenant-scoped IRS correspondence, newest first. A `client_user` is narrowed to its own entity memberships and fails CLOSED (empty page) with none. Free-text resolution/response bodies are not projected.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | optional | — |
status | query | string | optional | — |
notice_type | query | string | optional | — |
tax_year | query | string | optional | 4-digit year. An unparseable value is IGNORED, not an error. |
limit | query | string | optional | Default 50, capped at 100. |
offset | query | string | optional | Default 0, clamped non-negative. |
Responses
- 200One page of correspondence plus the exact pre-pagination total.
IrsCorrespondenceListResponseField Type Required Description correspondencearray<IrsCorrespondenceListItem>required — totalintegerrequired Exact count BEFORE pagination. 0 both for an empty book and for a `client_user` with no entity memberships (which fails closed to an empty page). - 403Authenticated but out of scope — a `client_user` asking for an entity outside its memberships (`FORBIDDEN_ENTITY`), or a Bearer key that does not carry the declared scope.
- 500A post-fetch tenant-scope leak was detected (defense-in-depth third wall) or the read failed. No partial data is returned.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/workers-comp/class-codesList workers-comp class codes
Class codes across the tenant's workers-comp policies, newest first. Entity / policy identity is lifted from a LEFT join on the parent policy. A `client_user` is narrowed to its own entities' policies and fails CLOSED (empty page) with none.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | optional | — |
state | query | string | optional | — |
class_code | query | string | optional | — |
limit | query | string | optional | Default 50, clamped to [1, 100]. |
offset | query | string | optional | Default 0, clamped non-negative. |
Responses
- 200One page of class codes plus the exact pre-pagination total.
WorkersCompClassCodesResponseField Type Required Description classCodesarray<WorkersCompClassCodeListItem>required — totalintegerrequired — - 403Authenticated but out of scope — a `client_user` asking for an entity outside its memberships (`FORBIDDEN_ENTITY`), or a Bearer key that does not carry the declared scope.
- 500A post-fetch tenant-scope leak was detected (defense-in-depth third wall) or the read failed. No partial data is returned.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/compliance/workers-compGet workers' comp summary
Tenant workers' comp summary. One row per employer: the live workers' comp NOTICES on file and the facts each one states (e-mod, class codes, audit period, carrier, policy, audited and deposit premium — each stated or null; `statedNotices: null` when the notices could not be read), plus expected losses from the class codes its ACTIVE policy states and the payroll on file over the experience period — never a guessed class code or rate. When a figure the arithmetic needs is not stated, the row is a named refusal (`dataSource: "pending_data"`, `missing_inputs`, no e-mod/premium); a row whose computation failed says so (`dataSource: "error"`, `error: "computation_failed"`, no figure). Tenant roll-ups are taken over rows that HAVE a figure and are null when none do. CPA-class callers only (firm-wide exposure across sibling employers).
audit.readResponses
- 200Workers' comp summary by employer (computed rows, named refusals and failed rows share one shape)
WorkersCompSummaryResponseField Type Required Description experiencePeriodWorkersCompSummaryExperiencePeriodrequired — entitiesarray<WorkersCompSummaryEntity>required — tenantSummaryWorkersCompSummaryTenantSummaryrequired — noticesWorkersCompSummaryNoticeTallyrequired — - 401Unauthorized
ComplianceUnauthorizedResponse - 403Forbidden — missing `audit.read` scope, or the caller is not CPA-class (a client_user session or an employer-minted / unattributable Bearer key)
ComplianceUnauthorizedResponse - 429Rate limit exceeded
ComplianceRateLimitResponse - 500Server error — a failed entities / policy / class-code read, a tenant-isolation check (any read, notices included), or a failed creator-role lookup (sanitized)
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Connections11
GET/api/connectionsList connections
Returns the unified connection list (data_connections + finch_connections) scoped to the caller's tenant. Sensitive config fields (encrypted refresh tokens, token expiries) are stripped.
payroll.readResponses
- 200Connections list
ConnectionListResponse - 401Unauthorized
ConnectionsUnauthorizedResponse - 403Forbidden — missing required scope
ConnectionsUnauthorizedResponse - 429Rate limit exceeded
ConnectionsRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/connections/{id}Trigger connection sync
Trigger a manual sync. Body: `{ action: "sync" }`. Enqueues a connection_sync job; the worker picks it up.
payroll.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Connection UUID |
Request body ConnectionPostBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
action | enum: "sync" | required | — |
Responses
- 200Sync enqueued
ConnectionSyncResponseField Type Required Description okenum: truerequired — messageenum: "Sync enqueued"required — - 400Validation error / wrong status / missing token
ConnectionsValidationErrorResponse - 401Unauthorized
ConnectionsUnauthorizedResponse - 403Forbidden — missing required scope
ConnectionsUnauthorizedResponse - 404Connection not found in caller's tenant
ConnectionsNotFoundResponse - 429Rate limit exceeded
ConnectionsRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
DELETE/api/connections/{id}Disconnect connection
Disconnect a connection: revoke the Google OAuth token (best-effort) and mark the row as `revoked`. Sensitive config fields are cleared.
payroll.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Connection UUID |
Responses
- 200Disconnected
ConnectionDisconnectResponseField Type Required Description okenum: truerequired — messageenum: "Disconnected"required — - 400Validation error
ConnectionsValidationErrorResponse - 401Unauthorized
ConnectionsUnauthorizedResponse - 403Forbidden — missing required scope
ConnectionsUnauthorizedResponse - 404Connection not found in caller's tenant
ConnectionsNotFoundResponse - 429Rate limit exceeded
ConnectionsRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/connections/finch/statusGet Finch connection status
Returns the Finch connection status + recent sync logs for an entity.
payroll.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | required | Entity UUID (required) |
Responses
- 200Status + sync logs
FinchStatusResponseField Type Required Description connectedbooleanrequired — statusstringrequired — degradedbooleanoptional — degraded_reasonstringoptional — connectionobjectrequired — recent_syncsarray<FinchSyncLog>optional — - 400Validation error
ConnectionsValidationErrorResponse - 401Unauthorized
ConnectionsUnauthorizedResponse - 403Forbidden — missing required scope
ConnectionsUnauthorizedResponse - 429Rate limit exceeded
ConnectionsRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/connections/{id}/foldersList provider folders
List folders from the connected provider (Google Drive or Dropbox). Returns selectedFolderIds from current config so the UI can render the checkbox state.
payroll.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Connection UUID |
Responses
- 200Folder list + selected IDs
ConnectionFoldersGetResponseField Type Required Description foldersarray<ConnectionFolderEntry>required — selectedFolderIdsarray<string>required — - 400Validation error
ConnectionsValidationErrorResponse - 401Unauthorized
ConnectionsUnauthorizedResponse - 403Forbidden — missing required scope
ConnectionsUnauthorizedResponse - 404Connection not found in caller's tenant
ConnectionsNotFoundResponse - 429Rate limit exceeded
ConnectionsRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PUT/api/connections/{id}/foldersSave selected folders
Save selected watched-folder IDs to the connection config.
payroll.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Connection UUID |
Request body ConnectionFoldersPutBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
folderIds | array<string> | required | — |
Responses
- 200Saved
ConnectionFoldersPutResponseField Type Required Description savedenum: truerequired — folderIdsarray<string>required — - 400Validation error
ConnectionsValidationErrorResponse - 401Unauthorized
ConnectionsUnauthorizedResponse - 403Forbidden — missing required scope
ConnectionsUnauthorizedResponse - 404Connection not found in caller's tenant
ConnectionsNotFoundResponse - 429Rate limit exceeded
ConnectionsRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/connections/{id}/watchList watched folders
List active watched folders for a connection.
payroll.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Connection UUID |
Responses
- 200Watched folders
ConnectionWatchListResponseField Type Required Description foldersarray<object>required — - 400Validation error
ConnectionsValidationErrorResponse - 401Unauthorized
ConnectionsUnauthorizedResponse - 403Forbidden — missing required scope
ConnectionsUnauthorizedResponse - 429Rate limit exceeded
ConnectionsRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/connections/{id}/watchStart watching folder
Start watching a folder for inbound notice intake. Verifies that both the connection and target entity belong to the caller's tenant.
payroll.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Connection UUID |
Request body ConnectionWatchPostBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
folder_id | string | required | — |
folder_name | string | required | — |
folder_path | string | optional | — |
entity_id | string · uuid | required | — |
Responses
- 200Now watching
ConnectionWatchPostResponseField Type Required Description watched_folderobjectrequired — entity_namestringrequired — messagestringrequired — - 400Validation error
ConnectionsValidationErrorResponse - 401Unauthorized
ConnectionsUnauthorizedResponse - 403Forbidden — missing required scope
ConnectionsUnauthorizedResponse - 404Connection or entity not found
ConnectionsNotFoundResponse - 429Rate limit exceeded
ConnectionsRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
DELETE/api/connections/{id}/watchStop watching folder
Stop watching a folder (soft-delete via is_active=false).
payroll.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Connection UUID |
Request body ConnectionWatchDeleteBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
watched_folder_id | string · uuid | required | — |
Responses
- 200Stopped
objectField Type Required Description successenum: truerequired — - 400Validation error
ConnectionsValidationErrorResponse - 401Unauthorized
ConnectionsUnauthorizedResponse - 403Forbidden — missing required scope
ConnectionsUnauthorizedResponse - 429Rate limit exceeded
ConnectionsRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/connections/finch/syncTrigger Finch sync
Trigger a manual Finch sync. Enqueues a finch_sync job; skips if another sync is already pending/running. Cookie sessions go through the Stripe subscription gate; Bearer keys bypass.
payroll.writeRequest body FinchSyncPostBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
Responses
- 200Sync enqueued or already in progress
FinchSyncResponseField Type Required Description messagestringrequired — job_idstring · uuidoptional — - 400Validation error / connection inactive
ConnectionsValidationErrorResponse - 401Unauthorized
ConnectionsUnauthorizedResponse - 402Subscription required (cookie sessions only)
ConnectionsUnauthorizedResponse - 403Forbidden — missing required scope
ConnectionsUnauthorizedResponse - 404No Finch connection for entity
ConnectionsNotFoundResponse - 429Rate limit exceeded
ConnectionsRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/connections/finch/statesDetected payroll work states for one entity
Distinct US work states detected on the entity's payroll records, used to seed state-registration recommendations. The entity is verified in the caller's tenant first; a `client_user` is additionally narrowed to its own entity. An unknown, foreign-tenant or sibling entity all answer the SAME 404 body — the refusal is not an existence oracle.
payroll.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | required | Required. Must be a UUID naming an entity in the caller's tenant. |
Responses
- 200Detected states. The ONLY 200 shape — a failed payroll read answers 500, not an empty list.
FinchDetectedStatesResponseField Type Required Description entity_idstring · uuidrequired The verified entity the states were derived from. statesarray<string>required Distinct, sorted, upper-cased 2-letter work states present on the entity's payroll records. `UNKNOWN` and non 2-letter values are dropped. EMPTY means exactly one thing — the entity has no synced work states — because a failed read now answers 500, not 200. countintegerrequired `states.length`. - 400`entity_id` missing or not a UUID.
- 403Authenticated but out of scope — a `client_user` asking for an entity outside its memberships (`FORBIDDEN_ENTITY`), or a Bearer key that does not carry the declared scope.
- 404Entity not found in the caller's tenant, or out of a `client_user`'s scope. One body for all three inputs.
- 500The entity read failed, the `payroll_records` read failed (inbox 262 — NOT a 200 with an empty list), or a post-fetch tenant-scope leak was detected (defense-in-depth third wall). No partial data is returned.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Dashboard2
GET/api/dashboard/summaryDashboard summary
Aggregated dashboard metrics (KRT-473): notice counts, task counts, fine amounts, and configured deadline window.
notices.readResponses
- 200Summary metrics
DashboardSummaryResponseField Type Required Description noticesobjectrequired — tasksobjectrequired — finesobjectrequired — filing_timelinessobjectrequired — deadline_window_daysintegerrequired — - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/dashboard/trendsDashboard trends
Per-day notice intake (notices received, by `created_at`) over a window of `days` days (1–90, default 30). Optionally filter to a single entity_id. Refused for employer (client_user) sessions and for keys an employer created.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
days | query | integer | optional | — |
entity_id | query | string · uuid | optional | — |
Responses
- 200Trends
DashboardTrendsResponseSchemaField Type Required Description windowDaysintegerrequired Days in the window: `days` clamped to 1–90, or 30 when `days` is missing or not a positive number. startDatestringrequired First day of the window (YYYY-MM-DD). endDatestringrequired Last day of the window, today (YYYY-MM-DD). trendsDashboardTrendSeriesrequired — - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Documents13
GET/api/documents/statsGet document counts
Returns document counts by status (total / pending / signed / expired) for the caller's tenant. Falls back to count-only when the documents.status column is missing.
notices.readResponses
- 200Stats by status
DocumentStatsResponseField Type Required Description totalintegerrequired — pendingintegerrequired — signedintegerrequired — expiredintegerrequired — - 401Unauthorized
DocumentUnauthorizedResponse - 403Forbidden — missing required scope
DocumentUnauthorizedResponse - 429Rate limit exceeded
DocumentRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/documents/collectionGet document-collection progress by entity
Returns document_requests grouped by entity, with progress counts (items_total / items_complete) and derived status (complete / overdue / in_progress).
notices.readResponses
- 200Grouped collection requests
DocumentCollectionResponseField Type Required Description requestsarray<DocumentCollectionGroup>required — - 401Unauthorized
DocumentUnauthorizedResponse - 403Forbidden — missing required scope
DocumentUnauthorizedResponse - 429Rate limit exceeded
DocumentRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/documentsList documents
List documents with optional filters (requestId, entityId, date range, includeDeleted) and pagination.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
requestId | query | string · uuid | optional | — |
entityId | query | string · uuid | optional | — |
startDate | query | string · date-time | optional | — |
endDate | query | string · date-time | optional | — |
limit | query | string | optional | — |
offset | query | string | optional | — |
includeDeleted | query | enum: "true" | "false" | optional | — |
Responses
- 200Documents list
DocumentListResponseField Type Required Description documentsarray<DocumentMetadata>required — pendingSignaturesobjectrequired — signedSignaturesobjectrequired — canResendSigningLinksbooleanrequired — signingEmailStatusAvailablebooleanrequired — countintegerrequired — limitintegerrequired — offsetintegerrequired — - 400Validation error
DocumentValidationErrorResponse - 401Unauthorized
DocumentUnauthorizedResponse - 403Forbidden — missing required scope
DocumentUnauthorizedResponse - 429Rate limit exceeded
DocumentRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/documents/{id}Get document detail
Get document metadata by id. document-engine internally scopes by RLS on the caller's session.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Document UUID |
Responses
- 200Document metadata
DocumentDetailResponseField Type Required Description documentDocumentMetadatarequired — - 400Validation error
DocumentValidationErrorResponse - 401Unauthorized
DocumentUnauthorizedResponse - 403Forbidden — missing required scope
DocumentUnauthorizedResponse - 404Document not found
DocumentNotFoundResponse - 429Rate limit exceeded
DocumentRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
DELETE/api/documents/{id}Soft-delete document
Soft-delete a document with an optional reason. Returns 204 No Content on success. Already-deleted documents return 409 Conflict.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Document UUID |
Request body DocumentDeleteBody
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | optional | — |
Responses
- 204Soft-deleted
- 400Validation error
DocumentValidationErrorResponse - 401Unauthorized
DocumentUnauthorizedResponse - 403A valid Bearer key without `notices.write`, a session that must complete an MFA step-up (`mfa_required`), or `role_forbidden` — a non-CPA cookie role, refused before any read or write (the same body for every id)
NoticesWriteForbiddenResponse - 404Document not found / not accessible
DocumentNotFoundResponse - 409Document already deleted
DocumentValidationErrorResponse - 429Rate limit exceeded
DocumentRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/documents/{id}/voidVoid document
Void a document with a reason. Voiding is a status transition, not a delete: the row, its storage object and its audit history all survive. Already-voided, completed and soft-deleted documents are refused with 409.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Document UUID |
Request body DocumentVoidBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | required | — |
Responses
- 200Voided
DocumentVoidResponseField Type Required Description voidedenum: truerequired — idstring · uuidrequired — - 400Validation error
DocumentValidationErrorResponse - 401Unauthorized
DocumentUnauthorizedResponse - 403A valid Bearer key without `notices.write`, a session that must complete an MFA step-up (`mfa_required`), or `role_forbidden` — a non-CPA cookie role, refused before any read or write (the same body for every id)
NoticesWriteForbiddenResponse - 404Not found or not accessible in this tenant
DocumentNotFoundResponse - 409Document is already voided, completed, or deleted
DocumentValidationErrorResponse - 429Rate limit exceeded
DocumentRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/documents/bulk/voidBulk void documents
Void up to 200 selected documents. Partial success by design: each id runs through the same per-document primitive, so non-transitionable ids are returned in `skipped[]` while the valid ids still void. Each void writes its own audit event.
notices.writeRequest body DocumentBulkVoidBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | required | — |
reason | string | required | — |
Responses
- 200Per-item outcome
DocumentBulkVoidResponseField Type Required Description voidedintegerrequired — skippedarray<object>required — failedarray<object>required — - 400Validation error
DocumentValidationErrorResponse - 401Unauthorized
DocumentUnauthorizedResponse - 403Forbidden — CPA-level access required
DocumentUnauthorizedResponse - 429Rate limit exceeded
DocumentRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/documents/request-emailSend document-request email
Send a document-request email to an employer contact via Resend. Sender is the caller's display_name + tenant name.
notices.writeRequest body DocumentRequestEmailBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
request_id | string · uuid | required | — |
recipient_email | string · email | required | — |
recipient_name | string | optional | — |
Responses
- 200Email sent (`sent: true`), or NOT sent with no provider call (`sent: false, suppressed: true`): `reason: "customer_sends_disabled"` when customer sends are switched off on this deployment, no `reason` when the non-prod recipient allowlist suppressed the address.
DocumentRequestEmailResponse - 400Validation error
DocumentValidationErrorResponse - 401Unauthorized
DocumentUnauthorizedResponse - 403Forbidden — missing required scope
DocumentUnauthorizedResponse - 404Entity or document request not found in caller's tenant
DocumentNotFoundResponse - 409The document request has no upload portal yet (W28-D2-4-3) — generate the portal link before sending.
DocumentValidationErrorResponse - 429Rate limit exceeded
DocumentRateLimitResponse - 500Server error / Resend API failure
one of (SanitizedServerError · AccountSetupIncompleteResponse) - 503Email service not configured
DocumentUnauthorizedResponse
POST/api/documents/uploadUpload document
Upload a document (multipart/form-data) for an entity, optionally tied to a document_request. Idempotency-aware (x-idempotency-key header). Tags accept JSON array or comma-separated form.
notices.writeRequest body DocumentUploadMultipartBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
file | string | required | Document binary |
entityId | string · uuid | optional | — |
requestId | string · uuid | optional | — |
tags | string | optional | JSON array or comma-separated tags |
Responses
- 200Uploaded
DocumentUploadResponseField Type Required Description successenum: truerequired — documentDocumentMetadatarequired — idempotent_replayenum: trueoptional — - 400Malformed form data, no `file`, a malformed `entityId` / `requestId`, a file too large / of a type not allowed / carrying unredactable sensitive data, or an idempotency refusal (a malformed key; `idempotency_body_unreadable`)
DocumentUploadRefusal - 401Unauthorized
DocumentUnauthorizedResponse - 403Forbidden — missing required scope
DocumentUnauthorizedResponse - 404Entity or request not found
DocumentNotFoundResponse - 409The same content is already on file for the tenant, or `idempotency_key_conflict` (the key was used for a different request)
DocumentUploadRefusal - 429Rate limit exceeded (uploads use the 10/min ceiling)
DocumentRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse) - 503The idempotency check itself failed, so a retry could not be told from a fresh request — refused before the handler ran (retryable)
SanitizedServerError
POST/api/documents/create-signing-requestCreate signing request link
Create a token-based signature request for a document and return a secure signing link suitable for email delivery. Token expires after 1-30 days.
notices.writeRequest body DocumentCreateSigningRequestBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
document_id | string · uuid | required | — |
entity_id | string · uuid | optional | The employer entity the signature request is filed under. REQUIRED for an employer (client_user) caller — omitting it answers 400 `INPUT_VALIDATION_FAILED` naming `entity_id` — and it must be one of that caller's own entities: any other id answers the same 404 `Document not found` as a missing document. Optional for CPA callers, where a null leaves the request unlinked. |
signer_name | string | required | — |
signer_email | string · email | required | — |
signer_title | string | optional | — |
expires_days | integer | optional | — |
Responses
- 201Signing request created
DocumentCreateSigningRequestResponseField Type Required Description request_idstring · uuidrequired — secure_tokenstring · uuidrequired — signing_linkstringrequired — expires_atstring · date-timerequired — - 400Validation error — including an employer (client_user) caller that omitted `entity_id` (`code: INPUT_VALIDATION_FAILED`, issue path `entity_id`).
DocumentCreateSigningRequestValidationError - 401Unauthorized
DocumentUnauthorizedResponse - 403Forbidden — missing required scope
DocumentUnauthorizedResponse - 404Document not found in caller's tenant — also the answer, byte-identical, for a document or body `entity_id` outside an employer caller's own entities (existence-opaque).
DocumentNotFoundResponse - 409Refused before any write: the document is not a PDF, has no confirmed employer or belongs to a different employer than `entity_id`; it is a government form with no mapped signature line (owner ruling S2: "This form has no signature line mapped yet — Kreto cannot stamp it."); or — owner rule E1 — it is linked to a notice whose letter has not been approved by a CPA (`reason: letter_not_approved`).
DocumentCreateSigningRequestRefusal - 429Rate limit exceeded
DocumentRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/documents/verifyVerify document via AI
Run AI-based document verification on an uploaded file (PDF/PNG/JPG/DOCX, ≤10 MB). Two audit events recorded (requested + completed). Returns documentType + confidence + verified + flags.
notices.writeRequest body DocumentVerifyMultipartBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
file | string | required | PDF/PNG/JPG/DOCX binary (max 10 MB) |
expectedType | string | optional | — |
Responses
- 200Verification result
DocumentVerifyResultField Type Required Description okenum: truerequired — resultobjectrequired — - 400Validation error / unsupported MIME / unreadable file
DocumentValidationErrorResponse - 401Unauthorized
DocumentUnauthorizedResponse - 403Forbidden — missing required scope
DocumentUnauthorizedResponse - 413File too large (>10 MB)
DocumentValidationErrorResponse - 429Rate limit exceeded
DocumentRateLimitResponse - 500Server error / verification pipeline failure
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/check-duplicateCheck upload duplicate
Check if an uploaded file with the given SHA-256 content hash already exists in the caller's tenant (KRT-203). The body is caller-dependent: CPA/system callers also get the matching upload's id, filename and upload time, while employer-plane callers (`client_user` cookie sessions and `client_user`-minted Bearer keys) get `isDuplicate` alone — the matched row can belong to a sibling employer, and its filename identifies that employer. Only `isDuplicate` is guaranteed present. Cookie sessions go through rate-limit middleware.
notices.readRequest body CheckDuplicateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
contentHash | string | required | — |
Responses
- 200Duplicate check result
CheckDuplicateResponseField Type Required Description isDuplicatebooleanrequired — originalUploadIdstring · uuidoptional — originalFileNamestringoptional — uploadedAtstring · date-timeoptional — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/documents/bulk/exportExport documents as CSV
Export up to 1000 documents by id. Ids outside the caller's tenant (or outside a `client_user`'s entities) are simply absent from the file.
notices.readRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | required | Target ids, 1..1000. Over the cap the request is REFUSED, never silently truncated. |
Responses
- 200CSV attachment: `text/csv; charset=utf-8` with `Content-Disposition: attachment; filename="documents-export-<YYYY-MM-DD>.csv"`. Header row, in order: `document_id`, `filename`, `kind`, `status`, `entity_id`, `request_id`, `notice_id`, `uploaded_at`.
string - 400Body failed validation — `ids` outside 1..1000, or a value that is not a UUID.
- 403Authenticated but out of scope — a `client_user` asking outside its entity memberships, or a Bearer key that does not carry the declared scope.
- 500The read failed, or a post-fetch tenant-scope leak was detected (defense-in-depth third wall). No partial file is written.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Employer Invites1
POST/api/employer-invites/{token}/acceptAccept a CPA firm's employer invitation
The signed-in invitee accepts a live, pending invitation: their profile moves into the inviting firm's tenant as `client_user`, the entity link and the firm/employer relationship are recorded, and the invitation is marked accepted. Session cookie only (no Bearer). The 200 body reports per-stage `steps`. A standalone employer whose own workspace already holds data is refused with 409 `SELF_TENANT_HAS_DATA` and nothing changes.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
token | path | string | required | The plaintext invitation capability token. |
Responses
- 200Invitation accepted (see `steps` for per-stage outcome)
EmployerInviteAcceptResponseField Type Required Description relationship_idstring · uuidrequired — entity_idstring · uuidrequired — stepsobjectrequired One status per non-fatal stage. `invite_status: failed` means the invitation is still pending and the link remains redeemable. messagestringrequired — - 401No session
EmployerInviteUnauthorizedResponse - 403A precondition refused a live invitation: `SELF_ACCEPTANCE_NOT_ALLOWED`, `EMAIL_MISMATCH`, `ROLE_CONFLICT`, or the session's MFA step-up (`mfa_required`)
one of (EmployerInviteRefusalResponse · EmployerInviteMfaRequiredResponse) - 404The one closed response: unknown, malformed, expired, revoked, or accepted-by-someone-else tokens are indistinguishable by design
EmployerInviteErrorOnly - 409Either the invitee re-clicking their own already-accepted link (`{ error }`), or `SELF_TENANT_HAS_DATA` — the caller's standalone workspace holds notices/records the move would strand; nothing changed
one of (EmployerInviteErrorOnly · EmployerInviteSelfTenantHasDataResponse) - 429Per-IP cap on the invitation-token plane
EmployerInviteErrorOnly - 500Sanitized server error
SanitizedServerError
Employers18
POST/api/employersCreate employer
Create a new employer entity for the authenticated CPA's tenant. Validates the body against the multi-axis Zod schema (legal_form, engagement_type, payroll_provider). Returns 201 Created with the new entity ID + legal_name.
Request body CreateEmployerBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
legal_name | string | required | — |
ein | string | optional | — |
ein_last4 | string | optional | — |
primary_state | string | optional | — |
address | string | optional | — |
city | string | optional | — |
state | string | optional | — |
zip | string | optional | — |
contact_name | string | optional | — |
contact_email | string · email | optional | — |
contact_phone | string | optional | — |
employee_count | integer | optional | — |
legal_form | enum: "llc" | "s_corp" | "sole_prop" | "partnership" | … | optional | — |
engagement_type | enum: "full_managed" | "selective" | "employer_direct" | optional | — |
payroll_provider | enum: "adp" | "gusto" | "paychex" | "quickbooks" | … | optional | — |
Responses
- 201Employer created
CreateEmployerResponseField Type Required Description entityEntitySummaryrequired — - 400Validation error
ValidationErrorResponse - 401Unauthorized
UnauthorizedResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PATCH/api/employersUpdate employer
Update an entity's contact info or core identifiers. Tenant-scoped via explicit tenant_id filter (KRT-1191 audit fix).
Request body PatchEmployerBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
contact_name | string | optional | — |
contact_email | string · email | optional | — |
contact_phone | string | optional | — |
legal_name | string | optional | — |
ein_last4 | string | optional | — |
primary_state | string | optional | — |
Responses
- 200Employer updated
PatchEmployerResponseField Type Required Description entityobjectrequired — - 400Validation error / no fields to update
ValidationErrorResponse - 401Unauthorized
UnauthorizedResponse - 404Entity not found in caller's tenant
NotFoundResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/employers/listList employers
List employers visible to the caller (RLS + explicit tenant filter). Each row carries notice_count, active_notices_count, overdue_notices_count, states_operated, and POA progress. Bearer-key callers need the `employers.read` scope (or NULL for full tenant scope).
employers.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
include_deleted | query | enum: "true" | "false" | optional | When `true`, include soft-deleted entities (CPA-class roles only). |
today | query | string | optional | The reader's local calendar day (YYYY-MM-DD) the compliance monitor's overdue reads use for `compliance_score`. Defaults to the server's day. |
Responses
- 200Employer list
EmployerListResponseField Type Required Description entitiesarray<EmployerListItem>required — - 401Unauthorized
UnauthorizedResponse - 403Forbidden — missing required scope
UnauthorizedResponse - 429Rate limit exceeded
RateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (EmployersListServerError · AccountSetupIncompleteResponse)
GET/api/employers/{id}/detailGet employer detail
Return the full entity row with associated notices, POA records, and a derived I-9 compliance summary. All Supabase queries filter by tenant_id explicitly (KRT-1479 P4a Senior-Dev-Override fix).
employers.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Employer entity UUID |
Responses
- 200Employer detail
EmployerDetailResponseField Type Required Description idstring · uuidrequired — tenant_idstring · uuidrequired — legal_namestringrequired — ein_last4stringrequired — primary_statestringrequired — entity_typestringrequired — contact_namestringrequired — contact_emailstringrequired — contact_phonestringrequired — created_atstring · date-timerequired — is_deletedbooleanoptional — deletion_datestring · date-timeoptional — deleted_atstring · date-timeoptional — deletion_scheduled_atstring · date-timeoptional — formation_datestring · dateoptional — noticesarray<NoticeSummary>required — poa_recordsarray<PoaRecord>required — i9_compliancearray<I9ComplianceEntry>required — penalties_ytdnumberrequired — - 400Validation error
ValidationErrorResponse - 401Unauthorized
UnauthorizedResponse - 403Forbidden — missing required scope
UnauthorizedResponse - 404Entity not found in caller's tenant
NotFoundResponse - 429Rate limit exceeded
RateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/employers/update-einUpdate employer EIN
Set the last 4 digits of an entity's EIN, optionally update legal_name, and clear `needs_ein` flag on a referenced notice. Tenant-scoped.
Request body UpdateEinBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
ein_last4 | string | optional | — |
legal_name | string | optional | — |
notice_id | string · uuid | optional | — |
Responses
- 200Updated
SuccessResponseField Type Required Description successenum: truerequired — - 400Validation error
ValidationErrorResponse - 401Unauthorized
UnauthorizedResponse - 404Entity not found in caller's tenant
NotFoundResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PATCH/api/employers/{id}/email-slugCustomize employer notice email
One-time customization of an entity's notice email slug. Once set, the slug is locked (`email_slug_customized=true`) and further changes require operator intervention.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Employer entity UUID |
Request body EmailSlugBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
slug | string | required | — |
Responses
- 200Slug set
EmailSlugResponseField Type Required Description successenum: truerequired — emailstringrequired — - 400Invalid slug format / already customized
ValidationErrorResponse - 401Unauthorized
UnauthorizedResponse - 404Entity not found in caller's tenant
NotFoundResponse - 409Slug taken by another entity
ValidationErrorResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/employers/{id}/deleteUndo employer soft-delete
Undo a soft-delete. Clears `deleted_at` + `deletion_scheduled_at`. Must be called within the 30-day grace window before /api/employers/cleanup permanently removes the entity row + cascade dependents.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Employer entity UUID |
Responses
- 200Deletion cancelled
UndoDeleteResponseField Type Required Description successenum: truerequired — entity_idstring · uuidrequired — - 400Entity is not deleted
ValidationErrorResponse - 401Unauthorized
UnauthorizedResponse - 404Entity not found in caller's tenant
NotFoundResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
DELETE/api/employers/{id}/deleteSoft-delete employer
Soft-delete an entity. Sets `deleted_at` + `deletion_scheduled_at` (now + 30 days). Permanent deletion is run by /api/employers/cleanup (internal cron). Reversible via POST to the same endpoint within the grace window.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Employer entity UUID |
Responses
- 200Deletion scheduled
DeleteEmployerResponseField Type Required Description successenum: truerequired — scheduled_permanent_deletionstring · date-timerequired — - 401Unauthorized
UnauthorizedResponse - 404Entity not found in caller's tenant
NotFoundResponse - 409Entity already scheduled for deletion
ValidationErrorResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/employers/{id}/separationsList an employer's separations
Separation events for one employer, newest first (capped at 100; totalCount is the exact total). A CPA reads the claimant's name; an employer user reads "Name withheld" in its place. No SSN or SSN hash is returned to anyone.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | Employer (entities) UUID |
Responses
- 200Separations — the CPA body, or the employer body (claimant name withheld)
one of (EmployerSeparationsEmployer · EmployerSeparationsCpa) - 400The id is not a UUID
EmployerSeparationsInvalidId - 401Unauthorized
MiscUnauthorizedResponse - 404Employer not found (unknown, foreign and non-member ids alike)
EmployerSeparationsNotFound - 409The employer was removed
EmployerSeparationsRemoved - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/entities/{id}/payroll-name-confirmationRead the name confirmation
Read the standing name-identity confirmation for this employer (who confirmed, when) and the estimate label it gives. Cookie session only, CPA roles only.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | Employer (entities) UUID |
Responses
- 200The standing decision
PayrollNameConfirmationStateField Type Required Description entity_idstringrequired — confirmedbooleanrequired — confirmed_bystringrequired Display name of the confirming CPA; null when none stands confirmed_atstringrequired ISO timestamp of the standing confirmation; null when none labelstringrequired The estimate label for this employer's name-keyed figures, with 'confirmed by <CPA> on <date>' while a confirmation stands. - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA access required (employers are refused)
MiscUnauthorizedResponse - 404Employer not found (unknown and foreign ids alike)
PayrollNameConfirmationNotFound - 500Server error — including a refused audit append or confirmation read
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/entities/{id}/payroll-name-confirmationConfirm no two employees share a name
Record the CPA's confirmation that no two employees of this employer share a name, so a name-keyed payroll estimate may support a protest letter. Cookie session only, CPA roles only.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | Employer (entities) UUID |
Responses
- 200Confirmation recorded
PayrollNameConfirmationResponseField Type Required Description entity_idstringrequired — confirmedbooleanrequired — labelstringrequired The estimate label every surface shows — with 'confirmed by <CPA> on <date>' after a confirmation. Still an estimate. - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA access required (employers are refused)
MiscUnauthorizedResponse - 404Employer not found (unknown and foreign ids alike)
PayrollNameConfirmationNotFound - 500Server error — including a refused audit append or confirmation read
one of (SanitizedServerError · AccountSetupIncompleteResponse)
DELETE/api/entities/{id}/payroll-name-confirmationWithdraw the name confirmation
Withdraw the name-identity confirmation; the estimate's letters are held again. Cookie session only, CPA roles only.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | Employer (entities) UUID |
Responses
- 200Confirmation withdrawn
PayrollNameConfirmationResponseField Type Required Description entity_idstringrequired — confirmedbooleanrequired — labelstringrequired The estimate label every surface shows — with 'confirmed by <CPA> on <date>' after a confirmation. Still an estimate. - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA access required (employers are refused)
MiscUnauthorizedResponse - 404Employer not found (unknown and foreign ids alike)
PayrollNameConfirmationNotFound - 500Server error — including a refused audit append or confirmation read
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/entities/{id}/payroll-start-dateRead mid-year start answers
Read the standing mid-year payroll start answers for this employer, per state and year. Cookie session only; CPA roles, or an employer user who is a member of this employer.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | Employer (entities) UUID |
Responses
- 200The standing answers
PayrollStartDateStateField Type Required Description entity_idstringrequired — decisionsarray<object>required — notesarray<object>required Mid-year starts detected today on the payroll on file, one per state + year - 400The body is not a valid answer for this caller
PayrollStartDateInvalidBody - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — a role outside CPA roles and client_user
MiscUnauthorizedResponse - 404Employer not found (unknown, foreign and non-member ids alike)
PayrollStartDateNotFound - 500Server error — including a refused audit append or read
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/entities/{id}/payroll-start-dateAnswer the mid-year start question
Answer whether this employer started paying wages on the date its payroll on file starts. CPA: yes (confirm), no, reverse. Employer: yes (stated by the employer), no. A CPA's answer outranks the employer's. Cookie session only.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | Employer (entities) UUID |
Request body PayrollStartDateAnswer
| Field | Type | Required | Description |
|---|---|---|---|
state | string | required | — |
year | integer | required | — |
start | string | required | — |
answer | enum: "yes" | "no" | "reverse" | required | Employers may answer yes or no; only a CPA may reverse |
Responses
- 200Answer recorded (or the CPA's answer stands)
PayrollStartDateRecordedField Type Required Description entity_idstringrequired — recordedbooleanrequired false when an employer answered a question its CPA has already answered (the CPA's answer governs; it is not disclosed) reruns_enqueuedintegeroptional Open notices queued for recalculation by this answer; null when the re-run could not be queued (absent when nothing was recorded) statusstringrequired — - 400The body is not a valid answer for this caller
PayrollStartDateInvalidBody - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — a role outside CPA roles and client_user
MiscUnauthorizedResponse - 404Employer not found (unknown, foreign and non-member ids alike)
PayrollStartDateNotFound - 500Server error — including a refused audit append or read
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/employers/bulk/archiveBulk archive or unarchive employers
Set `archived` on up to 200 employers. Soft-deleted employers are out of scope unless `include_deleted` is explicitly true.
employers.writeRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | required | Target ids, 1..200. Duplicates are collapsed before any write. |
archived | boolean | optional | — |
include_deleted | boolean | optional | Opt in to mutating SOFT-DELETED employers. Absent or false (what every product surface sends) means a removed employer is not a target. |
Responses
- 200Per-id ledger. Partial success is a 200.
BulkActionResultField Type Required Description appliedintegerrequired Ids that produced a real state change (and therefore exactly one audit row each). skippedarray<object>required Ids deliberately not mutated. Present on a 200 — partial success is the contract, not an error. failedarray<object>required Ids whose mutation raised. `probe_failed` means the tenant-scoped existence probe itself errored, so nothing was attempted. - 400Body failed validation (ids outside 1..200, or a bad uuid).
- 403Authenticated but not CPA-class (`FORBIDDEN_ROLE`), or the Bearer key lacks the declared scope.
- 500A tenant-scope leak was detected post-fetch (defense-in-depth third wall). No writes occurred.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/employers/bulk/assignBulk assign employers to a firm user
Assign up to 200 employers to one in-tenant user, or clear the assignee with `null`. The assignee is validated against `user_profiles` in-tenant first.
employers.writeRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | required | Target ids, 1..200. Duplicates are collapsed before any write. |
assignee | string · uuid | required | — |
include_deleted | boolean | optional | Opt in to mutating SOFT-DELETED employers. Absent or false (what every product surface sends) means a removed employer is not a target. |
Responses
- 200Per-id ledger. Partial success is a 200.
BulkActionResultField Type Required Description appliedintegerrequired Ids that produced a real state change (and therefore exactly one audit row each). skippedarray<object>required Ids deliberately not mutated. Present on a 200 — partial success is the contract, not an error. failedarray<object>required Ids whose mutation raised. `probe_failed` means the tenant-scoped existence probe itself errored, so nothing was attempted. - 400Body failed validation (ids outside 1..200, or a bad uuid).
- 403Authenticated but not CPA-class (`FORBIDDEN_ROLE`), or the Bearer key lacks the declared scope.
- 500A tenant-scope leak was detected post-fetch (defense-in-depth third wall). No writes occurred.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/employers/bulk/tagBulk add, remove or set employer tags
Apply up to 20 tags to up to 200 employers. `mode` defaults to `add`; `set` replaces the existing tag list.
employers.writeRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | required | Target ids, 1..200. Duplicates are collapsed before any write. |
tags | array<string> | required | — |
mode | enum: "add" | "remove" | "set" | optional | — |
include_deleted | boolean | optional | Opt in to mutating SOFT-DELETED employers. Absent or false (what every product surface sends) means a removed employer is not a target. |
Responses
- 200Per-id ledger. Partial success is a 200.
BulkActionResultField Type Required Description appliedintegerrequired Ids that produced a real state change (and therefore exactly one audit row each). skippedarray<object>required Ids deliberately not mutated. Present on a 200 — partial success is the contract, not an error. failedarray<object>required Ids whose mutation raised. `probe_failed` means the tenant-scoped existence probe itself errored, so nothing was attempted. - 400Body failed validation (ids outside 1..200, or a bad uuid).
- 403Authenticated but not CPA-class (`FORBIDDEN_ROLE`), or the Bearer key lacks the declared scope.
- 500A tenant-scope leak was detected post-fetch (defense-in-depth third wall). No writes occurred.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/employers/bulk/exportExport employers as CSV
Export up to 1000 employers by id. The `status` cell carries ONE lifecycle vocabulary (`active` | `archived` | `removed`), so a soft-deleted employer cannot read as active. EINs are emitted masked (`***-NNNN`), never in full.
employers.readRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | required | Target ids, 1..1000. Over the cap the request is REFUSED, never silently truncated. |
include_deleted | boolean | optional | CPA-only opt-in to including SOFT-DELETED employers. Absent or false (what every product surface sends) leaves them out. |
Responses
- 200CSV attachment: `text/csv; charset=utf-8` with `Content-Disposition: attachment; filename="employers-export-<YYYY-MM-DD>.csv"`. Header row, in order: `employer`, `ein_masked`, `primary_state`, `entity_type`, `compliance_score`, `tags`, `status`, `created`.
string - 400Body failed validation — `ids` outside 1..1000, or a value that is not a UUID.
- 403Authenticated but out of scope — a `client_user` asking outside its entity memberships, or a Bearer key that does not carry the declared scope.
- 500The read failed, or a post-fetch tenant-scope leak was detected (defense-in-depth third wall). No partial file is written.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Escalations3
POST/api/escalations/bulk/acknowledgeBulk acknowledge escalations
Acknowledge up to 200 escalations, keyed by notice id. An already-acknowledged escalation is skipped with `no_change`; a cross-tenant or unknown id is skipped with `not_found`. client_user is refused.
notices.writeRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | required | Target ids, 1..200. Duplicates are collapsed before any write. |
Responses
- 200Per-id ledger. Partial success is a 200.
BulkActionResultField Type Required Description appliedintegerrequired Ids that produced a real state change (and therefore exactly one audit row each). skippedarray<object>required Ids deliberately not mutated. Present on a 200 — partial success is the contract, not an error. failedarray<object>required Ids whose mutation raised. `probe_failed` means the tenant-scoped existence probe itself errored, so nothing was attempted. - 400Body failed validation (ids outside 1..200, or a bad uuid).
- 403Authenticated but not CPA-class (`FORBIDDEN_ROLE`), or the Bearer key lacks the declared scope.
- 500A tenant-scope leak was detected post-fetch (defense-in-depth third wall). No writes occurred.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/escalations/bulk/snoozeBulk snooze escalations
Snooze up to 200 escalations. `snooze_hours` defaults to the route's own default when omitted. client_user is refused.
notices.writeRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | required | Target ids, 1..200. Duplicates are collapsed before any write. |
snooze_hours | integer | optional | — |
Responses
- 200Per-id ledger. Partial success is a 200.
BulkActionResultField Type Required Description appliedintegerrequired Ids that produced a real state change (and therefore exactly one audit row each). skippedarray<object>required Ids deliberately not mutated. Present on a 200 — partial success is the contract, not an error. failedarray<object>required Ids whose mutation raised. `probe_failed` means the tenant-scoped existence probe itself errored, so nothing was attempted. - 400Body failed validation (ids outside 1..200, or a bad uuid).
- 403Authenticated but not CPA-class (`FORBIDDEN_ROLE`), or the Bearer key lacks the declared scope.
- 500A tenant-scope leak was detected post-fetch (defense-in-depth third wall). No writes occurred.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/escalations/bulk/reassignBulk reassign escalations
Reassign up to 200 escalations to one in-tenant user, or clear the assignee with `null`. The assignee is validated in-tenant before any write.
notices.writeRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | required | Target ids, 1..200. Duplicates are collapsed before any write. |
assignee | string · uuid | required | — |
Responses
- 200Per-id ledger. Partial success is a 200.
BulkActionResultField Type Required Description appliedintegerrequired Ids that produced a real state change (and therefore exactly one audit row each). skippedarray<object>required Ids deliberately not mutated. Present on a 200 — partial success is the contract, not an error. failedarray<object>required Ids whose mutation raised. `probe_failed` means the tenant-scoped existence probe itself errored, so nothing was attempted. - 400Body failed validation (ids outside 1..200, or a bad uuid).
- 403Authenticated but not CPA-class (`FORBIDDEN_ROLE`), or the Bearer key lacks the declared scope.
- 500A tenant-scope leak was detected post-fetch (defense-in-depth third wall). No writes occurred.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Garnishments2
GET/api/garnishmentsList garnishment orders
List garnishment orders for the caller's tenant (compliance category #6 — wage garnishments). client_user is entity-scoped on cookie sessions; Bearer keys see full tenant scope.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | optional | — |
status | query | string | optional | — |
order_type | query | string | optional | — |
limit | query | integer | optional | — |
offset | query | integer | optional | — |
Responses
- 200Garnishments
GarnishmentsListResponseField Type Required Description garnishmentsarray<GarnishmentOrder>required — totalintegerrequired — - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — entity access denied (client_user scope)
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/garnishmentsCreate garnishment order
Create a garnishment order. Bearer keys gate via `notices.write`. client_user is entity-scoped on cookie sessions.
notices.writeRequest body GarnishmentCreateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
notice_id | string · uuid | optional | — |
order_type | string | optional | — |
court_order_number | string | optional | — |
case_number | string | optional | — |
issuing_authority | string | optional | — |
employee_identifier | string | optional | — |
employee_count | integer | optional | — |
garnishment_amount | number | optional | — |
withholding_percentage | number | optional | — |
max_cumulative | number | optional | — |
priority_order | integer | optional | — |
effective_date | string | optional | — |
expiration_date | string | optional | — |
response_deadline | string | optional | — |
Responses
- 201Garnishment created
GarnishmentCreateResponseField Type Required Description garnishmentGarnishmentOrderrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — entity access denied
MiscUnauthorizedResponse - 404Entity not found in caller's tenant
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Handbooks5
GET/api/handbooks/{id}Get one handbook with its sections and coverage gaps
The read behind the draft door: the handbook row, its pinned sections in document order, and the coverage gaps the generator recorded. Read-only — publishing is a separate approval route. A `client_user` (and any Bearer key minted by one) is narrowed to its own entity memberships: a handbook outside them answers the same 404 as an id that does not exist, never a 403. Gated per tenant by the `HANDBOOKS_ENABLED` flag; off answers a plain 404.
handbooks.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | — |
Responses
- 200The handbook, its sections and its gaps.
HandbookDetailResponseField Type Required Description handbookHandbookRowrequired — sectionsarray<HandbookSectionView>required Pinned sections in document order. Pins whose module version no longer resolves are NOT dropped — they are counted in `unresolved_section_count`, because a shorter list would hide a hole in the document a reviewer is being asked to publish. gapsarray<HandbookGap>required — unresolved_section_countintegerrequired — - 400`id` is not a UUID (`VALIDATION_FAILED`).
- 401No valid credentials: no cookie session and no Bearer key, or a Bearer key that is malformed, unknown, revoked or expired.
- 403The auth wrapper's own refusals only: a valid Bearer key that does not carry `handbooks.read` (`Forbidden — missing required scope: handbooks.read` — never a 401), or a session that must complete an MFA step-up (`mfa_required`). Never an entity-scope decision — a `client_user` asking for a handbook outside its entity memberships is answered the 404 below (W47-HBAPI3, existence-opaque).
HandbookDetailForbiddenResponse - 404No such handbook in the caller's tenant (`NOT_FOUND`) — and the SAME body for a foreign-tenant id, a sibling entity's handbook outside a `client_user`'s memberships, and a non-published handbook on the employer plane. Handbooks disabled for the tenant answers a plain `{ error: "Not found" }`.
HandbookNotFoundResponse - 500The caller's entity scope could not be resolved (fail closed), or the read failed. No partial data is returned.
one of (SanitizedServerErrorWithCode · AccountSetupIncompleteResponse)
POST/api/handbooks/{id}/publishPublish a draft handbook
Thin shim over the `publishHandbook` L2 method (APPROVAL_REQUIRED). The §0-2(b) blocking disclaimer checkbox is part of the contract: `disclaimerAccepted` must be `true`. CPA-class only; gated per tenant by the `HANDBOOKS_ENABLED` flag (off answers a plain 404).
handbooks.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | — |
Request body object
| Field | Type | Required | Description |
|---|---|---|---|
disclaimerAccepted | boolean | required | — |
Responses
- 200Published: `{ handbook }` — the row as the publish wrote it (`status` `published`, `published_at` + `disclaimer_ack_at/by` stamped, `pdf_status` `queued`).
HandbookPublishResponseField Type Required Description handbookHandbookRowrequired — - 400`id` is not a UUID, the body is not JSON, or `disclaimerAccepted` is not a boolean (`VALIDATION_FAILED`).
- 401No valid credentials, or the API key's creator no longer exists (`AUTH_KEY_CREATOR_MISSING`).
- 402Cookie session whose tenant has no active subscription. Bearer keys are not gated here.
- 403A `client_user` session (`FORBIDDEN_ROLE`), a Bearer key without `handbooks.write`, or a session that must complete an MFA step-up.
- 404No such handbook in the caller's tenant (`NOT_FOUND`). Handbooks disabled for the tenant answers a plain `{ error: "Not found" }`.
- 409The draft cannot be published. `HANDBOOK_HAS_NO_SECTIONS`: it has no policy sections — `error` is the sentence for the CPA and `missing` names the jurisdictions and required topics the verified catalog lacks (the same code the /v1 twin answers). `ILLEGAL_TRANSITION`: any other illegal transition (the handbook is not a draft). Nothing is written on either arm.
HandbookPublishRefusal - 422`disclaimerAccepted` is `false` — the blocking checkbox was not ticked (`DISCLAIMER_REQUIRED`).
- 500The publish failed: the sanitized body `ctx.finalize` leaves with (`code` `INTERNAL_ERROR`, generic `error`, `requestId` when one was minted). A cookie session with no `user_profiles` row answers the `withApiAuth` wrapper's `AccountSetupIncompleteResponse` instead.
one of (SanitizedServerErrorWithCode · AccountSetupIncompleteResponse)
POST/api/admin/handbook-modules/promotePromote a handbook module version or registry topic
Thin shim over `promoteHandbookModuleVersion` / `promoteStateRequiredTopic` (APPROVAL_REQUIRED, UI-only). The reviewer identity is the authenticated admin, never the body. The ladder is monotonic (unverified → human_reviewed → verified).
Request body one of (object · object)
application/json
Responses
- 200Promoted: the row as the promote wrote it, with the ladder move.
HandbookModulePromoteResponse - 400The body is not JSON, or it failed the method's input schema (`VALIDATION_FAILED`, with up to ten zod `details` when the schema rejected it).
- 401No admin session (`requireAdminAPI`).
- 403The session is not a system administrator, or must complete an MFA step-up (`requireAdminAPI`).
- 404No such module version or registry row (`NOT_FOUND`).
HandbookRecordNotFound - 409`PROVENANCE_REQUIRED`: the row has no `source_url`. `ILLEGAL_TRANSITION`: the version is emergency-retired, or the move is not up the ladder. Nothing is written.
HandbookModulePromoteRefusal - 500Unexpected failure: the sanitized body `captureApiError` writes (generic `error`, `requestId` when one was minted). An admin session with no `user_profiles` row answers `requireAdminAPI`'s `AccountSetupIncompleteResponse` instead.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/admin/handbook-modules/retireEmergency-retire a handbook module version
Thin shim over `retireHandbookModuleVersion` (APPROVAL_REQUIRED, UI-only) — the §0-5d emergency pull. Reason mandatory; existing pins stay reconstructable.
Request body object
| Field | Type | Required | Description |
|---|---|---|---|
moduleVersionId | string · uuid | required | — |
reason | string | required | — |
Responses
- 200Retired: `{ version }` with `retired_at` stamped.
HandbookModuleRetireResponseField Type Required Description versionHandbookModuleVersionRecordrequired — - 400The body is not JSON, or it failed the method's input schema (`VALIDATION_FAILED`, with up to ten zod `details` when the schema rejected it).
- 401No admin session (`requireAdminAPI`).
- 403The session is not a system administrator, or must complete an MFA step-up (`requireAdminAPI`).
- 404No such module version (`NOT_FOUND`).
HandbookRecordNotFound - 409`ILLEGAL_TRANSITION`: the version is already retired. Nothing is written.
HandbookIllegalTransitionRefusal - 500Unexpected failure: the sanitized body `captureApiError` writes (generic `error`, `requestId` when one was minted). An admin session with no `user_profiles` row answers `requireAdminAPI`'s `AccountSetupIncompleteResponse` instead.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/handbooks/{id}/acknowledgmentsSend a published handbook for employee acknowledgment
Thin shim over `sendHandbookAcknowledgments`. Writes one evidence row per reachable employee and mails the acknowledgment link; employees already sent are not re-mailed. CPA-class only; gated per tenant by the `HANDBOOKS_ENABLED` flag.
handbooks.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | — |
Responses
- 200This round's outcome, counted per send.
HandbookAcknowledgmentsSendResponseField Type Required Description creatednumberrequired — skippedExistingnumberrequired — cannotReacharray<object>required — emailFailuresnumberrequired — sendOutcomesobjectrequired — - 400`id` is not a UUID (`VALIDATION_FAILED`).
- 401No valid credentials, or the API key's creator no longer exists (`AUTH_KEY_CREATOR_MISSING`).
- 402Cookie session whose tenant has no active subscription. Bearer keys are not gated here.
- 403A `client_user` session (`FORBIDDEN_ROLE`), a Bearer key without `handbooks.write`, or a session that must complete an MFA step-up.
- 404No such handbook in the caller's tenant (`NOT_FOUND`). Handbooks disabled for the tenant answers a plain `{ error: "Not found" }`.
HandbookAcknowledgmentsNotFound - 409`ILLEGAL_TRANSITION`: the handbook is not published — only published handbooks are sent. Nothing is written.
HandbookIllegalTransitionRefusal - 500The send failed: the sanitized body `ctx.finalize` leaves with (`code` `INTERNAL_ERROR`, generic `error`, `requestId` when one was minted). A cookie session with no `user_profiles` row answers the `withApiAuth` wrapper's `AccountSetupIncompleteResponse` instead.
one of (SanitizedServerErrorWithCode · AccountSetupIncompleteResponse)
I-912
POST/api/i9-compliance/uploadStore an I-9 document
Store a single I-9 document for an employee. Any authenticated cookie session may file — the CPA plane and the employer plane alike — and authorization is the caller's entity allow-set, not their role name; an employee outside it answers 404, never 403. Bearer keys gate via `notices.write`. Multipart body with `file` and `employee_id` (UUID) fields. The document is written to the private `i9-documents` bucket and linked to an `i9_records` row with a computed `retention_until`, as ONE compensated unit — a failure is a named 4xx/5xx and leaves nothing behind, never a success receipt. Every 200 carries `mode: "persisted"` and `persisted: true`. The response returns a path PREFIX, never an object key or a signed URL.
notices.writeRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
file | string · binary | required | — |
employee_id | string | required | — |
Responses
- 200Document stored — object and i9_records row both written
I9UploadResponseField Type Required Description successenum: truerequired — modeenum: "persisted"required — persistedenum: truerequired — employee_idstringrequired — filenamestringrequired — record_idstringrequired The i9_records row the document is attached to. storage_path_prefixstringrequired The FOLDER the object was written to (<tenant_id>/<entity_id>/<i9_record_id>/) — never the object key and never a signed URL. Fetch documents through GET /api/i9-compliance/forms/{id}/documents. retention_untilstringrequired 8 CFR 274a.2(b)(2)(i)(A): three years after hire or one year after termination, whichever is later. retention_provisionalbooleanrequired True when the date was computed from an unknown (no termination date, or no hire date) and is therefore a FLOOR to be recomputed. - 400Validation error
I9ValidationErrorResponse - 401Unauthorized
I9UnauthorizedResponse - 403Forbidden — employer access required / missing scope
I9UnauthorizedResponse - 429Rate limit exceeded
I9RateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/i9-compliance/forms/{id}/documentsList (or mint a download for) one I-9 record's documents
Without `download`, answers SLOTS and counts only — original filenames are people's names and do not travel. With `download=pdf` or `download=<index>`, mints a short-lived signed URL for that one slot; the slot is the ONLY way a storage path is chosen, so a caller cannot name an arbitrary object. CPA-class roles only on cookie sessions.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | — |
download | query | string | optional | `pdf`, or the zero-based index of a supporting document. Absent ⇒ the slot listing. |
Responses
- 200The slot listing, or the signed-URL grant when `download` is given.
one of (I9DocumentSlotsResponse · I9DocumentDownloadResponse) - 400`id` is not a UUID, or `download` is neither `pdf` nor a valid index.
- 401No valid credentials: no cookie session and no Bearer key, or a Bearer key that is malformed, unknown, revoked or expired.
- 403A valid Bearer key that does not carry `notices.read` (`Forbidden — missing required scope: notices.read` — never a 401); a session that must complete an MFA step-up (`mfa_required`); or a caller that is not CPA-class — a non-CPA cookie session, or a Bearer key whose minting user is an employer or cannot be resolved (`Forbidden — CPA-level access required`).
I9DocumentsForbiddenResponse - 404No such I-9 record in the caller's tenant, or no document in that slot.
- 500The record read failed.
one of (SanitizedServerError · AccountSetupIncompleteResponse) - 502The signed-URL mint failed.
POST/api/i9-compliance/forms/{id}/documentsUpload Section 2 supporting document
Upload a Section 2 supporting document for an I-9 record. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`. Multipart body with `file`, `list` (a|b|c), and `index` (0..10).
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | I-9 record UUID |
Request body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
file | string · binary | required | — |
list | enum: "a" | "b" | "c" | required | — |
index | integer | required | — |
Responses
- 201Document uploaded
I9RecordResponseField Type Required Description recordI9Recordrequired — - 400Validation error
I9ValidationErrorResponse - 401Unauthorized
I9UnauthorizedResponse - 403Forbidden — CPA access required
I9UnauthorizedResponse - 404I-9 record not found
I9NotFoundResponse - 429Rate limit exceeded
I9RateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/i9-compliance/forms/{id}/pdfDownload I-9 PDF
Render the I-9 record as a flattened PDF. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | I-9 record UUID |
Responses
- 200PDF binary
I9PdfBinaryResponse - 401Unauthorized
I9UnauthorizedResponse - 403Forbidden — CPA access required
I9UnauthorizedResponse - 404I-9 record not found
I9NotFoundResponse - 429Rate limit exceeded
I9RateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/i9-compliance/forms/{id}Get I-9 record
Get a single I-9 record. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | I-9 record UUID |
Responses
- 200I-9 record
I9RecordResponseField Type Required Description recordI9Recordrequired — - 400Validation error
I9ValidationErrorResponse - 401Unauthorized
I9UnauthorizedResponse - 403Forbidden — CPA access required
I9UnauthorizedResponse - 404I-9 record not found
I9NotFoundResponse - 429Rate limit exceeded
I9RateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PATCH/api/i9-compliance/forms/{id}Update I-9 Section 2
Update an I-9 record's Section 2 data (employer review). Validates List A is mutually exclusive with List B/C. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | I-9 record UUID |
Request body I9Section2Bodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
list_a_docs | array<I9DocumentEntry> | optional | — |
list_b_docs | array<I9DocumentEntry> | optional | — |
list_c_docs | array<I9DocumentEntry> | optional | — |
reviewer_name | string | required | — |
reviewer_title | string | required | — |
date_of_hire | string | required | — |
date_of_completion | string | required | — |
Responses
- 200I-9 record updated
I9RecordResponseField Type Required Description recordI9Recordrequired — - 400Validation error / invalid document combination
I9ValidationErrorResponse - 401Unauthorized
I9UnauthorizedResponse - 403Forbidden — CPA access required
I9UnauthorizedResponse - 404I-9 record not found
I9NotFoundResponse - 409Section 1 must be completed first
I9ValidationErrorResponse - 429Rate limit exceeded
I9RateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/i9-compliance/formsList I-9 records by employee
List I-9 records for a given employee. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
employee_id | query | string · uuid | required | — |
Responses
- 200I-9 records
I9RecordsListResponseField Type Required Description recordsarray<I9Record>required — - 400Validation error
I9ValidationErrorResponse - 401Unauthorized
I9UnauthorizedResponse - 403Forbidden — CPA access required
I9UnauthorizedResponse - 429Rate limit exceeded
I9RateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/i9-compliance/formsCreate I-9 record (Section 1)
Create an I-9 record (Section 1). Cross-field validation: LPR requires alien_registration_number; authorized_alien requires at least one of A-Number / I-94 / passport+country. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`.
notices.writeRequest body I9Section1CreateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
employee_id | string · uuid | required | — |
citizenship_status | I9CitizenshipStatus | required | — |
section1_data | I9Section1Data | required | — |
ssn | string | optional | — |
Responses
- 201I-9 record created
I9RecordResponseField Type Required Description recordI9Recordrequired — - 400Validation error
I9ValidationErrorResponse - 401Unauthorized
I9UnauthorizedResponse - 403Forbidden — CPA access required
I9UnauthorizedResponse - 404Employee not found in the caller's tenant (unknown and foreign-tenant ids answer identically)
I9NotFoundResponse - 429Rate limit exceeded
I9RateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/i9-compliance/{employeeId}Get employee I-9 detail
Get a single employee's I-9 details + uploaded documents. Falls back to payroll_pay_periods when employeeId is an SSN hash. cookie sessions: client_user is entity-scoped; CPA roles see all. Bearer keys gate via `notices.read` (full tenant access).
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
employeeId | path | string | required | Employee UUID or SSN hash |
Responses
- 200Employee + documents
I9EmployeeDetailResponseField Type Required Description employeeI9EmployeeI9required — documentsarray<I9DocumentRow>required — sourceenum: "employees" | "payroll_pay_periods"required — - 401Unauthorized
I9UnauthorizedResponse - 403Not authorized for this employee (entity-scope check)
I9UnauthorizedResponse - 404Employee not found
I9NotFoundResponse - 429Rate limit exceeded
I9RateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/i9-compliance/bulk-uploadMatch I-9 documents to employees (preview only)
Bulk upload up to 50 I-9 documents. Extracts employee names from filenames and fuzzy-matches against the active roster (entity-scoped for client_user; full tenant for CPA / Bearer keys with `notices.write`). PREVIEW ONLY: returns matched + unmatched results and persists NOTHING — no storage object, no i9_records row, no audit entry. Every 200 carries `mode: "match_preview"` and `persisted: false`.
notices.writeRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
files | array<string · binary> | required | — |
Responses
- 200Match results
I9BulkUploadResponseField Type Required Description modeenum: "match_preview"required — persistedenum: falserequired — totalintegerrequired — matchedarray<I9BulkMatch>required — unmatchedarray<I9BulkUnmatched>required — - 400Validation error
I9ValidationErrorResponse - 401Unauthorized
I9UnauthorizedResponse - 429Rate limit exceeded
I9RateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/i9-complianceList I-9 employees
List I-9 employee records for the caller's tenant + expiry stats. Falls back to payroll_pay_periods when no employees rows exist. Bearer keys gate via `notices.read`.
notices.readResponses
- 200Employee list + expiry stats
I9EmployeesListResponseField Type Required Description employeesarray<I9EmployeeListItem>required — expiry_statsobjectoptional — employment_statsobjectoptional — sourcestringoptional — messagestringoptional — - 401Unauthorized
I9UnauthorizedResponse - 429Rate limit exceeded
I9RateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/i9-complianceCreate I-9 employee
Create a new I-9 employee record. Employer-only on cookie sessions; Bearer keys gate via `notices.write`.
notices.writeRequest body I9EmployeeCreateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
employee_name | string | required | — |
hire_date | string | required | — |
entity_id | string · uuid | required | — |
email | string · email | optional | — |
department | string | optional | — |
Responses
- 201Employee created
I9EmployeeCreateResponseField Type Required Description employeeI9EmployeeListItemrequired — - 400Validation error
I9ValidationErrorResponse - 401Unauthorized
I9UnauthorizedResponse - 403Forbidden — employer access required
I9UnauthorizedResponse - 404Entity not found in caller's tenant
I9NotFoundResponse - 429Rate limit exceeded
I9RateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse) - 501Employees table not provisioned yet
I9UnauthorizedResponse
KnowledgeBase1
GET/api/knowledge-baseQuery state compliance knowledge
Query the global state_compliance_knowledge table — Kreto-wide reference data covering state-by-state compliance rules, notice types, and deadlines. No tenant filter (read-only reference data). Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
state | query | string | optional | — |
type | query | string | optional | — |
q | query | string | optional | — |
min_confidence | query | number | optional | — |
limit | query | integer | optional | — |
offset | query | integer | optional | — |
Responses
- 200Knowledge entries + summary
KnowledgeBaseResponseField Type Required Description entriesarray<KnowledgeBaseEntry>required — totalintegerrequired — summaryobjectrequired — - 400Unparseable search term
MiscUnauthorizedResponse - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
l2:agency-documents5
POST/v1/agency-documents.listList an employer's agency documents (state rate notices and charge statements Kreto read), one per document with its newest reading and its status. The accountant sees each figure with its page and quote; the employer sees the status word only. Tenant- and employer-scoped; read-only. AUTONOMOUS.
List an employer's agency documents (state rate notices and charge statements Kreto read), one per document with its newest reading and its status. The accountant sees each figure with its page and quote; the employer sees the status word only. Tenant- and employer-scoped; read-only. AUTONOMOUS.
notices.readRequest body AgencyDocumentsListInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string · uuid | required | — |
Responses
- 200Successful invocation
AgencyDocumentsListOutputField Type Required Description documentsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/agency-documents.getRead one agency-document reading: the form, the dates it states and its status; the accountant also sees each figure with its page, quote and confidence. Tenant- and employer-scoped; read-only. AUTONOMOUS.
Read one agency-document reading: the form, the dates it states and its status; the accountant also sees each figure with its page, quote and confidence. Tenant- and employer-scoped; read-only. AUTONOMOUS.
notices.readRequest body AgencyDocumentsGetInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
extractionId | string · uuid | required | — |
Responses
- 200Successful invocation
AgencyDocumentsGetOutputField Type Required Description documentobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/agency-documents.confirmThe accountant confirms an agency document's figures (each with its page and quote), rejecting any that were misread (each with a reason from a closed list; a rejected figure is kept on the reading but never used, and re-opens only by typing the printed value). Confirmed figures are applied to the notice they are usable for — only when the document's date is the state's computation date for that rate year — as the agency's own figures, never as Kreto-verified; a figure that differs from one the employer stated is held for the accountant's review. When every figure the rate check reads is on file, the rate decision re-runs through the job queue. A misread figure may be corrected to the value printed on the page; Kreto keeps its own reading and quote beside the accountant's value. Accountant-only, tenant- and employer-scoped. NOTIFY.
The accountant confirms an agency document's figures (each with its page and quote), rejecting any that were misread (each with a reason from a closed list; a rejected figure is kept on the reading but never used, and re-opens only by typing the printed value). Confirmed figures are applied to the notice they are usable for — only when the document's date is the state's computation date for that rate year — as the agency's own figures, never as Kreto-verified; a figure that differs from one the employer stated is held for the accountant's review. When every figure the rate check reads is on file, the rate decision re-runs through the job queue. A misread figure may be corrected to the value printed on the page; Kreto keeps its own reading and quote beside the accountant's value. Accountant-only, tenant- and employer-scoped. NOTIFY.
notices.writeRequest body AgencyDocumentsConfirmInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
extractionId | string · uuid | required | — |
noticeId | string · uuid | optional | — |
reject | array<object> | optional | — |
edit | array<object> | optional | — |
Responses
- 200Successful invocation
AgencyDocumentsConfirmOutputField Type Required Description confirmedenum: truerequired — extractionIdstringrequired — acceptedarray<enum: "years_in_business" | "total_benefit_charges" | "total_contributions" | "average_annual_payroll" | …>required — rejectedarray<enum: "years_in_business" | "total_benefit_charges" | "total_contributions" | "average_annual_payroll" | …>required — editedarray<enum: "years_in_business" | "total_benefit_charges" | "total_contributions" | "average_annual_payroll" | …>required — projectionsarray<object>required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/agency-documents.rejectFigureThe accountant rejects one figure Kreto read on an agency document, before confirming it, with a reason from a closed list (misread_value, wrong_line, wrong_period, not_on_document, illegible, other — `other` needs a short note). Kreto's reading and quote are kept; the figure is never applied to a notice, and it re-opens only when the accountant types the value printed on the page in the confirm. Accountant-only, tenant- and employer-scoped. NOTIFY.
The accountant rejects one figure Kreto read on an agency document, before confirming it, with a reason from a closed list (misread_value, wrong_line, wrong_period, not_on_document, illegible, other — `other` needs a short note). Kreto's reading and quote are kept; the figure is never applied to a notice, and it re-opens only when the accountant types the value printed on the page in the confirm. Accountant-only, tenant- and employer-scoped. NOTIFY.
notices.writeRequest body AgencyDocumentsRejectFigureInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
extractionId | string · uuid | required | — |
noticeId | string · uuid | optional | — |
key | enum: "years_in_business" | "total_benefit_charges" | "total_contributions" | "average_annual_payroll" | … | required | — |
reason | enum: "misread_value" | "wrong_line" | "wrong_period" | "not_on_document" | … | required | — |
note | string | optional | — |
Responses
- 200Successful invocation
AgencyDocumentsRejectFigureOutputField Type Required Description rejectedenum: truerequired — extractionIdstringrequired — keyenum: "years_in_business" | "total_benefit_charges" | "total_contributions" | "average_annual_payroll" | …required — reasonenum: "misread_value" | "wrong_line" | "wrong_period" | "not_on_document" | …required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/agency-documents.acceptAllThe accountant confirms every figure of an agency document in one step — offered only when every figure was read with high confidence, its quote was found on the page, none was rejected and the reader's quote and date checks passed; otherwise refused as `bulk_requires_all_high` with each reason named (confirm figure by figure instead). Applied exactly like a confirm, with one audit entry per figure. Accountant-only, tenant- and employer-scoped. NOTIFY.
The accountant confirms every figure of an agency document in one step — offered only when every figure was read with high confidence, its quote was found on the page, none was rejected and the reader's quote and date checks passed; otherwise refused as `bulk_requires_all_high` with each reason named (confirm figure by figure instead). Applied exactly like a confirm, with one audit entry per figure. Accountant-only, tenant- and employer-scoped. NOTIFY.
notices.writeRequest body AgencyDocumentsAcceptAllInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
extractionId | string · uuid | required | — |
noticeId | string · uuid | optional | — |
Responses
- 200Successful invocation
AgencyDocumentsAcceptAllOutputField Type Required Description confirmedenum: truerequired — extractionIdstringrequired — acceptedarray<enum: "years_in_business" | "total_benefit_charges" | "total_contributions" | "average_annual_payroll" | …>required — rejectedarray<enum: "years_in_business" | "total_benefit_charges" | "total_contributions" | "average_annual_payroll" | …>required — editedarray<enum: "years_in_business" | "total_benefit_charges" | "total_contributions" | "average_annual_payroll" | …>required — projectionsarray<object>required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 409Conflict. `bulk_requires_all_high`: Accept all is withheld — `blocks` names each reason, one sentence each (the same entries as the reading's `bulk_accept_blocks`). Any other code: the reading is already confirmed or not read yet (the envelope names it).
one of (object · L2ErrorEnvelope) - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:annual-reports12
POST/v1/annual-reports.listAnnualReportRulesList the state annual-report catalog (global config): per-(state, entity_kind) SOS annual/biennial report rules with the verification ladder status of every cell and honest per-status counts. Cells below the display gate (human_reviewed) must never be rendered as authoritative to customers.
List the state annual-report catalog (global config): per-(state, entity_kind) SOS annual/biennial report rules with the verification ladder status of every cell and honest per-status counts. Cells below the display gate (human_reviewed) must never be rendered as authoritative to customers.
annual_reports.readRequest body AnnualReportsListAnnualReportRulesInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | optional | — |
entityKind | enum: "corporation" | "llc" | "nonprofit" | "lp" | … | optional | — |
verificationStatus | enum: "unverified" | "human_reviewed" | "verified" | optional | — |
Responses
- 200Successful invocation
AnnualReportsListAnnualReportRulesOutputField Type Required Description rulesarray<object>required — countsobjectrequired — filteredCountnumberrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `annual_reports.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/annual-reports.getAnnualReportRuleFetch one annual-report catalog cell with the (state, entity_kind) → (state, 'default') fallback. Returns the human-readable due-rule narrative alongside the raw rule. Null rule = no catalog coverage yet.
Fetch one annual-report catalog cell with the (state, entity_kind) → (state, 'default') fallback. Returns the human-readable due-rule narrative alongside the raw rule. Null rule = no catalog coverage yet.
annual_reports.readRequest body AnnualReportsGetAnnualReportRuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
entityKind | enum: "corporation" | "llc" | "nonprofit" | "lp" | … | optional | — |
Responses
- 200Successful invocation
AnnualReportsGetAnnualReportRuleOutputField Type Required Description ruleobjectrequired — usedDefaultFallbackbooleanrequired — dueNarrativestringrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `annual_reports.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/annual-reports.proposeAnnualReportRuleResearch-agent write path for the annual-report catalog. Upserts one (state, entity_kind) cell as verification_status='unverified' with mandatory provenance (source_url + retrieved_at). REFUSES to touch a cell already at human_reviewed/verified — verified cells are never downgraded (AC-22 clone); corrections go through the review queue.
Research-agent write path for the annual-report catalog. Upserts one (state, entity_kind) cell as verification_status='unverified' with mandatory provenance (source_url + retrieved_at). REFUSES to touch a cell already at human_reviewed/verified — verified cells are never downgraded (AC-22 clone); corrections go through the review queue.
annual_reports.writeRequest body AnnualReportsProposeAnnualReportRuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
entityKind | enum: "corporation" | "llc" | "nonprofit" | "lp" | … | optional | — |
agencyCode | string | required | — |
agencyName | string | required | — |
portalUrl | string · uri | optional | — |
formName | string | optional | — |
filingFeeUsd | number | optional | — |
frequency | enum: "annual" | "biennial" | "decennial" | "none" | required | — |
dueRule | one of (object · object · object · object · object) | required | — |
parity | enum: "odd" | "even" | "formation_year" | optional | — |
gracePeriodDays | integer | optional | — |
lateFeeNotes | string | optional | — |
sourceUrl | string · uri | required | — |
retrievedAt | string · date-time | required | — |
notes | string | optional | — |
Responses
- 200Successful invocation
AnnualReportsProposeAnnualReportRuleOutputField Type Required Description ruleobjectrequired — createdbooleanrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `annual_reports.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/annual-reports.promoteAnnualReportRulePromote an annual-report catalog cell along the verification ladder (unverified → human_reviewed → verified). APPROVAL_REQUIRED and UI-only: the handler rejects any non-human principal (AC-20 clone) and refuses cells with no source_url provenance (CWE-345).
Promote an annual-report catalog cell along the verification ladder (unverified → human_reviewed → verified). APPROVAL_REQUIRED and UI-only: the handler rejects any non-human principal (AC-20 clone) and refuses cells with no source_url provenance (CWE-345).
annual_reports.writeRequest body AnnualReportsPromoteAnnualReportRuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
entityKind | enum: "corporation" | "llc" | "nonprofit" | "lp" | … | optional | — |
targetStatus | enum: "human_reviewed" | "verified" | required | — |
reason | string | required | — |
Responses
- 200Successful invocation
AnnualReportsPromoteAnnualReportRuleOutputField Type Required Description ruleobjectrequired — fromStatusenum: "unverified" | "human_reviewed" | "verified"required — toStatusenum: "unverified" | "human_reviewed" | "verified"required — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden. `SYSTEM_ADMIN_REQUIRED`: the caller is not a Kreto system administrator on an interactive session — the global catalog every firm pins (handbook modules, state filing / foreign-qualification / annual-report / e-delivery rules) is staff-only, exactly as on the REST admin door. Checked before any catalog read, so the answer is the same whether or not the row exists. `STEP_UP_REQUIRED`: the dispatcher's APPROVAL_REQUIRED gate (an API key, or no fresh re-authentication). Nothing is written.
one of (L2SystemAdminRequiredRefusal · L2ErrorEnvelope) - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/annual-reports.listAnnualReportsList the tenant's annual-report tracker rows (per entity × state × filing period), filterable by entity, state, status and period. Paginated (default 25). Tenant-scoped via the invocation context.
List the tenant's annual-report tracker rows (per entity × state × filing period), filterable by entity, state, status and period. Paginated (default 25). Tenant-scoped via the invocation context.
annual_reports.readRequest body AnnualReportsListAnnualReportsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | optional | — |
stateCode | string | optional | — |
status | enum: "pending" | "prepared" | "submitted" | "approved" | … | optional | — |
periodLabel | string | optional | — |
limit | integer | optional | — |
offset | integer | optional | — |
Responses
- 200Successful invocation
AnnualReportsListAnnualReportsOutputField Type Required Description reportsarray<object>required — totalnumberrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `annual_reports.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/annual-reports.getAnnualReportByEntityFetch every annual-report tracker row for one entity (optionally one state), all periods — history plus in-flight. Tenant-scoped.
Fetch every annual-report tracker row for one entity (optionally one state), all periods — history plus in-flight. Tenant-scoped.
annual_reports.readRequest body AnnualReportsGetAnnualReportByEntityInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
stateCode | string | optional | — |
Responses
- 200Successful invocation
AnnualReportsGetAnnualReportByEntityOutputField Type Required Description reportsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `annual_reports.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/annual-reports.computeAnnualReportScheduleDry-run compute of an entity's annual-report schedule for one period year (default: current year): per candidate state, the computed due date OR an explicit non-computed disposition (needs_input / needs_research / below_display_gate / not_required / not_due_this_period). Writes nothing — the sweep's cycle roller persists rows; this method answers 'what would be due?'.
Dry-run compute of an entity's annual-report schedule for one period year (default: current year): per candidate state, the computed due date OR an explicit non-computed disposition (needs_input / needs_research / below_display_gate / not_required / not_due_this_period). Writes nothing — the sweep's cycle roller persists rows; this method answers 'what would be due?'.
annual_reports.readRequest body AnnualReportsComputeAnnualReportScheduleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
periodYear | integer | optional | — |
Responses
- 200Successful invocation
AnnualReportsComputeAnnualReportScheduleOutputField Type Required Description entityIdstringrequired — periodYearnumberrequired — itemsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `annual_reports.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/annual-reports.createAnnualReportCreate one manual annual-report tracker row (source='manual') for an entity × state × period. Computes the due date from the display-gated catalog when possible; otherwise the row starts manual_required with a NULL due date ('Kreto needs your input'). Duplicate periods are rejected by the per-period UNIQUE.
Create one manual annual-report tracker row (source='manual') for an entity × state × period. Computes the due date from the display-gated catalog when possible; otherwise the row starts manual_required with a NULL due date ('Kreto needs your input'). Duplicate periods are rejected by the per-period UNIQUE.
annual_reports.writeRequest body AnnualReportsCreateAnnualReportInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
stateCode | string | required | — |
periodLabel | string | required | — |
anchorKind | enum: "formation" | "foreign_qualification" | "manual" | optional | — |
anchorDate | string | optional | — |
notes | string | optional | — |
Responses
- 200Successful invocation
AnnualReportsCreateAnnualReportOutputField Type Required Description reportobjectrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `annual_reports.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/annual-reports.updateAnnualReportMetadataEdit tracker metadata on one annual-report row: anchor date/kind (recomputes the due date from the display-gated catalog and flips manual_required → pending when a date becomes computable), assignee and notes. Writes a structured audit event capturing before/after.
Edit tracker metadata on one annual-report row: anchor date/kind (recomputes the due date from the display-gated catalog and flips manual_required → pending when a date becomes computable), assignee and notes. Writes a structured audit event capturing before/after.
annual_reports.writeRequest body AnnualReportsUpdateAnnualReportMetadataInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
annualReportId | string | required | — |
anchorDate | string | optional | — |
anchorKind | enum: "formation" | "foreign_qualification" | "manual" | optional | — |
assignee | string | optional | — |
notes | string | optional | — |
reason | string | required | — |
Responses
- 200Successful invocation
AnnualReportsUpdateAnnualReportMetadataOutputField Type Required Description reportobjectrequired — changedFieldsarray<string>required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `annual_reports.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/annual-reports.prepareAnnualReportPopulate an annual report's prepared form payload (officers, principal address, the customer's OWN registered agent — Kreto is not the RA) and transition pending/manual_required → prepared. APPROVAL_REQUIRED: symmetric with prepareRegistrationForm; a human reviews before any filing. PII in form_payload is scrubbed from audit rows.
Populate an annual report's prepared form payload (officers, principal address, the customer's OWN registered agent — Kreto is not the RA) and transition pending/manual_required → prepared. APPROVAL_REQUIRED: symmetric with prepareRegistrationForm; a human reviews before any filing. PII in form_payload is scrubbed from audit rows.
annual_reports.writeRequest body AnnualReportsPrepareAnnualReportInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
annualReportId | string | required | — |
formPayload | object | required | — |
Responses
- 200Successful invocation
AnnualReportsPrepareAnnualReportOutputField Type Required Description reportobjectrequired — fromStatusenum: "pending" | "prepared" | "submitted" | "approved" | …required — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `annual_reports.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/annual-reports.requestConciergeFilingRecord the '$149 File it for me' concierge request on a prepared annual report: stamps filing_requested_at/by (design D3 — an annotation on 'prepared', not a status) and charges the $149 as a standalone Stripe invoice (created + finalized, so it bills every customer; owner 2026-07-10; fail-closed when STRIPE_SECRET_KEY is unset; double-bill safe). APPROVAL_REQUIRED: this moves money, so the v1 dispatcher blocks non-interactive API-key / MCP callers (KRT-1657) — it requires an interactive human session. The click by an authenticated CPA/owner IS the recorded filing authorization. The state filing fee is separate and paid to the state.
Record the '$149 File it for me' concierge request on a prepared annual report: stamps filing_requested_at/by (design D3 — an annotation on 'prepared', not a status) and charges the $149 as a standalone Stripe invoice (created + finalized, so it bills every customer; owner 2026-07-10; fail-closed when STRIPE_SECRET_KEY is unset; double-bill safe). APPROVAL_REQUIRED: this moves money, so the v1 dispatcher blocks non-interactive API-key / MCP callers (KRT-1657) — it requires an interactive human session. The click by an authenticated CPA/owner IS the recorded filing authorization. The state filing fee is separate and paid to the state.
annual_reports.writeRequest body AnnualReportsRequestConciergeFilingInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
annualReportId | string | required | — |
Responses
- 200Successful invocation
AnnualReportsRequestConciergeFilingOutputField Type Required Description reportobjectrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `annual_reports.write`.
L2ErrorEnvelope - 409Not available (code `STATE_FILING_NOT_AVAILABLE`): Kreto does not file annual reports yet. Nothing is written, nothing is charged, nothing is enqueued and the row's status does not change.
L2StateFilingNotAvailableRefusal - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/annual-reports.transitionAnnualReportStatusApply a matrix-gated status transition to an annual-report tracker row (exact esr 7-status machine). Supports the mark-filed extras (confirmationNumber + filedAt on → submitted) and the confirmedImmediately chain (submitted → approved in one audited call for instant-confirm states). Every hop writes a hash-chained audit_events row.
Apply a matrix-gated status transition to an annual-report tracker row (exact esr 7-status machine). Supports the mark-filed extras (confirmationNumber + filedAt on → submitted) and the confirmedImmediately chain (submitted → approved in one audited call for instant-confirm states). Every hop writes a hash-chained audit_events row.
annual_reports.writeRequest body AnnualReportsTransitionAnnualReportStatusInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
annualReportId | string | required | — |
newStatus | enum: "pending" | "prepared" | "submitted" | "approved" | … | required | — |
reason | string | required | — |
confirmationNumber | string | optional | — |
filedAt | string · date-time | optional | — |
confirmedImmediately | boolean | optional | — |
lastError | string | optional | — |
Responses
- 200Successful invocation
AnnualReportsTransitionAnnualReportStatusOutputField Type Required Description reportobjectrequired — fromStatusenum: "pending" | "prepared" | "submitted" | "approved" | …required — toStatusenum: "pending" | "prepared" | "submitted" | "approved" | …required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `annual_reports.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:audit1
POST/v1/audit.getRecentForRegistrationReturn recent service_audit_log rows linked to a specific state_registration row (via linked_resource_ids.registrationId). Tenant-scoped. AUTONOMOUS: append-only audit-table reads have no side effects.
Return recent service_audit_log rows linked to a specific state_registration row (via linked_resource_ids.registrationId). Tenant-scoped. AUTONOMOUS: append-only audit-table reads have no side effects.
audit.readRequest body AuditGetRecentForRegistrationInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
registrationId | string | required | — |
limit | integer | optional | — |
Responses
- 200Successful invocation
AuditGetRecentForRegistrationOutputField Type Required Description eventsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `audit.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:business-license5
POST/v1/business-license.listLicensesList the business licenses and local permits tracked for the tenant (optionally filtered to one entity), soft-deleted rows excluded, ordered by soonest expiration. Read-only. Kreto tracks licenses the employer already holds — it does not discover, obtain, or renew them.
List the business licenses and local permits tracked for the tenant (optionally filtered to one entity), soft-deleted rows excluded, ordered by soonest expiration. Read-only. Kreto tracks licenses the employer already holds — it does not discover, obtain, or renew them.
state_registration.readRequest body BusinessLicenseListLicensesInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | optional | — |
Responses
- 200Successful invocation
BusinessLicenseListLicensesOutputField Type Required Description licensesarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/business-license.createLicenseRecord a business license or local permit the employer already holds (manual tracker entry). Verifies the target entity belongs to the caller's tenant, inserts the row, and appends a hash-chained business_license.created audit event. NOTIFY classification: agents can call but the action is logged for human review.
Record a business license or local permit the employer already holds (manual tracker entry). Verifies the target entity belongs to the caller's tenant, inserts the row, and appends a hash-chained business_license.created audit event. NOTIFY classification: agents can call but the action is logged for human review.
state_registration.writeRequest body BusinessLicenseCreateLicenseInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
licenseName | string | required | — |
licenseType | enum: "state_business" | "city_business" | "county_business" | "professional" | … | required | — |
licenseNumber | string | optional | — |
issuingAuthority | string | required | — |
state | string | optional | — |
jurisdiction | string | optional | — |
status | enum: "active" | "expired" | "pending" | "suspended" | … | optional | — |
issuedDate | string | optional | — |
expirationDate | string | optional | — |
renewalDate | string | optional | — |
renewalFee | number | optional | — |
autoRenew | boolean | optional | — |
notes | string | optional | — |
noExpiry | boolean | optional | — |
Responses
- 200Successful invocation
BusinessLicenseCreateLicenseOutputField Type Required Description licenseobjectrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/business-license.updateLicenseEdit a tracked business license. Absent key = unchanged, null = clear (where the column is nullable). Appends a hash-chained business_license.updated audit event carrying per-field old → new. No-op submits write nothing. NOTIFY classification: agents can call but the action is logged for human review.
Edit a tracked business license. Absent key = unchanged, null = clear (where the column is nullable). Appends a hash-chained business_license.updated audit event carrying per-field old → new. No-op submits write nothing. NOTIFY classification: agents can call but the action is logged for human review.
state_registration.writeRequest body BusinessLicenseUpdateLicenseInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
licenseId | string | required | — |
licenseName | string | optional | — |
licenseType | enum: "state_business" | "city_business" | "county_business" | "professional" | … | optional | — |
licenseNumber | string | optional | — |
issuingAuthority | string | optional | — |
state | string | optional | — |
jurisdiction | string | optional | — |
status | enum: "active" | "expired" | "pending" | "suspended" | … | optional | — |
issuedDate | string | optional | — |
expirationDate | string | optional | — |
renewalDate | string | optional | — |
renewalFee | number | optional | — |
autoRenew | boolean | optional | — |
notes | string | optional | — |
Responses
- 200Successful invocation
BusinessLicenseUpdateLicenseOutputField Type Required Description licenseobjectrequired — changedFieldsarray<string>required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/business-license.recordRenewalRecord a license renewal the employer/CPA already completed with the issuing authority: moves the license's expiration date, recomputes status, and appends a license_renewal_history row plus a hash-chained business_license.renewed audit event. Kreto never performs the renewal itself. NOTIFY classification: agents can call but the action is logged for human review.
Record a license renewal the employer/CPA already completed with the issuing authority: moves the license's expiration date, recomputes status, and appends a license_renewal_history row plus a hash-chained business_license.renewed audit event. Kreto never performs the renewal itself. NOTIFY classification: agents can call but the action is logged for human review.
state_registration.writeRequest body BusinessLicenseRecordRenewalInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
licenseId | string | required | — |
newExpirationDate | string | required | — |
renewalCost | number | optional | — |
confirmationNumber | string | optional | — |
notes | string | optional | — |
Responses
- 200Successful invocation
BusinessLicenseRecordRenewalOutputField Type Required Description licenseobjectrequired — historyRecordedbooleanrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/business-license.deleteLicenseSoft-delete a tracked business license (sets deleted_at; the row stays for audit). Pending renewal-alert idempotency rows CASCADE on hard delete only — soft-deleted rows simply stop matching the reminder scan. Appends a hash-chained business_license.deleted audit event. NOTIFY classification: agents can call but the action is logged for human review.
Soft-delete a tracked business license (sets deleted_at; the row stays for audit). Pending renewal-alert idempotency rows CASCADE on hard delete only — soft-deleted rows simply stop matching the reminder scan. Appends a hash-chained business_license.deleted audit event. NOTIFY classification: agents can call but the action is logged for human review.
state_registration.writeRequest body BusinessLicenseDeleteLicenseInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
licenseId | string | required | — |
Responses
- 200Successful invocation
BusinessLicenseDeleteLicenseOutputField Type Required Description licenseIdstringrequired — deletedAtstringrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:catalog-watch3
POST/v1/catalog-watch.watchSourceCheck one watched catalog source now: Claude reads the cited agency page (server-side web fetch), code compares the figures with the catalog cell, and any difference is written as a PROPOSAL for the admin Review Queue. Never writes a catalog row. Kreto staff (system_admin session) only.
Check one watched catalog source now: Claude reads the cited agency page (server-side web fetch), code compares the figures with the catalog cell, and any difference is written as a PROPOSAL for the admin Review Queue. Never writes a catalog row. Kreto staff (system_admin session) only.
admin.writeRequest body CatalogWatchWatchSourceInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
watchId | string | required | — |
Responses
- 200Successful invocation
CatalogWatchWatchSourceOutputField Type Required Description watchIdstringrequired — statusstringrequired — modelstringrequired — findingsRaisedintegerrequired — findingIdsarray<string>required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/catalog-watch.listWatchFindingsList catalog-watch findings (proposals) for the admin Review Queue, newest first, with open/approved/dismissed counts and how many watched sources are failing. Kreto staff (system_admin session) only.
List catalog-watch findings (proposals) for the admin Review Queue, newest first, with open/approved/dismissed counts and how many watched sources are failing. Kreto staff (system_admin session) only.
admin.readRequest body CatalogWatchListWatchFindingsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
status | enum: "open" | "approved" | "dismissed" | optional | — |
family | enum: "filing_rule" | "filing_prereq" | "foreign_qual_rule" | "compliance_obligation" | optional | — |
stateCode | string | optional | — |
limit | integer | optional | — |
Responses
- 200Successful invocation
CatalogWatchListWatchFindingsOutputField Type Required Description findingsarray<object>required — countsobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/catalog-watch.resolveWatchFindingA Kreto staff member approves or dismisses one catalog-watch finding. Approve records the decision and returns the cell's EXISTING catalog edit page — it writes no catalog row. Dismiss needs a reason. Both append a hash-chained audit event. UI-only (APPROVAL_REQUIRED).
A Kreto staff member approves or dismisses one catalog-watch finding. Approve records the decision and returns the cell's EXISTING catalog edit page — it writes no catalog row. Dismiss needs a reason. Both append a hash-chained audit event. UI-only (APPROVAL_REQUIRED).
admin.writeRequest body CatalogWatchResolveWatchFindingInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
findingId | string | required | — |
decision | enum: "approve" | "dismiss" | required | — |
reason | string | optional | — |
Responses
- 200Successful invocation
CatalogWatchResolveWatchFindingOutputField Type Required Description findingobjectrequired — editHrefstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:consent2
POST/v1/consent.captureRecord the one-time blanket account-level consent that authorizes Kreto to store and use this account's portal credentials, with money movement explicitly enumerated (every payment still requires its own human approval — a hard floor). Idempotent: returns the existing active grant instead of duplicating.
Record the one-time blanket account-level consent that authorizes Kreto to store and use this account's portal credentials, with money movement explicitly enumerated (every payment still requires its own human approval — a hard floor). Idempotent: returns the existing active grant instead of duplicating.
admin.writeRequest body ConsentCaptureInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
acknowledged | enum: true | required | — |
Responses
- 200Successful invocation
ConsentCaptureOutputField Type Required Description grantobjectrequired — isNewGrantbooleanrequired — textVersionstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/consent.getRead the account's active blanket vault-consent grant (or null) plus the current consent text version. Use before storing a credential to check whether the consent precondition is satisfied.
Read the account's active blanket vault-consent grant (or null) plus the current consent text version. Use before storing a credential to check whether the consent precondition is satisfied.
admin.readRequest body ConsentGetInputrequired
application/json
Responses
- 200Successful invocation
ConsentGetOutputField Type Required Description grantobjectrequired — textVersionstringrequired — textstringrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:cpa4
POST/v1/cpa.listEmployersList the CPA's linked employers (entities) with per-employer notice counts. Paginated via `limit` (1-100, default 25) and zero-indexed `offset`. Optional case-insensitive `search` filters on legal_name. LIVE, non-archived employers only by default — pass `include_deleted` for removed (purge-scheduled) employers or `include_archived` for archived ones; every row carries `lifecycle` (live | archived | removed), and `total` counts the same set the rows come from. Tenant-scoped. Returns aggregates only — no PII (no signer info, no employee names, no notice subjects).
List the CPA's linked employers (entities) with per-employer notice counts. Paginated via `limit` (1-100, default 25) and zero-indexed `offset`. Optional case-insensitive `search` filters on legal_name. LIVE, non-archived employers only by default — pass `include_deleted` for removed (purge-scheduled) employers or `include_archived` for archived ones; every row carries `lifecycle` (live | archived | removed), and `total` counts the same set the rows come from. Tenant-scoped. Returns aggregates only — no PII (no signer info, no employee names, no notice subjects).
employers.readRequest body CpaListEmployersInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
limit | integer | optional | — |
offset | integer | optional | — |
search | string | optional | — |
include_deleted | boolean | optional | — |
include_archived | boolean | optional | — |
Responses
- 200Successful invocation
CpaListEmployersOutputField Type Required Description employersarray<object>required — totalintegerrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `employers.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/cpa.summarizeComplianceAggregate the CPA tenant's compliance posture: notice counts by status, per-employer rollup, fines (total/recovered/outstanding cents), and response-time average. Tenant-scoped. Returns aggregates only — no PII.
Aggregate the CPA tenant's compliance posture: notice counts by status, per-employer rollup, fines (total/recovered/outstanding cents), and response-time average. Tenant-scoped. Returns aggregates only — no PII.
employers.readRequest body CpaSummarizeComplianceInputrequired
application/json
Responses
- 200Successful invocation
CpaSummarizeComplianceOutputField Type Required Description generated_atstringrequired — tenant_idstringrequired — noticesobjectrequired — per_entityarray<object>required — finesobjectrequired — response_timeobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `employers.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/cpa.queryAuditEventsQuery the tenant's audit event timeline. Paginated via `limit` (1-100, default 25) and zero-indexed `offset`. Optional filters: `entity_id` (UUID), `notice_id` (UUID), `event_type` (exact match e.g. 'notice_created'), `since`/`until` (ISO datetimes). Tenant-scoped. `detail` is PII-redacted (SSN/EIN/bank patterns replaced) and capped at 500 chars. Metadata + integrity-chain columns are NOT returned.
Query the tenant's audit event timeline. Paginated via `limit` (1-100, default 25) and zero-indexed `offset`. Optional filters: `entity_id` (UUID), `notice_id` (UUID), `event_type` (exact match e.g. 'notice_created'), `since`/`until` (ISO datetimes). Tenant-scoped. `detail` is PII-redacted (SSN/EIN/bank patterns replaced) and capped at 500 chars. Metadata + integrity-chain columns are NOT returned.
audit.readRequest body CpaQueryAuditEventsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
limit | integer | optional | — |
offset | integer | optional | — |
entity_id | string | optional | — |
notice_id | string | optional | — |
event_type | string | optional | — |
since | string · date-time | optional | — |
until | string · date-time | optional | — |
Responses
- 200Successful invocation
CpaQueryAuditEventsOutputField Type Required Description eventsarray<object>required — totalintegerrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `audit.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/cpa.summarizeObligationsAggregate compliance-obligations rollup across the CPA's employer book. Paginated via `limit` (1-100, default 25) and zero-indexed `offset`; optional case-insensitive `search` on legal_name. For each employer: applicable / low-confidence / pending-data obligation counts plus the earliest computable due date; firm-wide totals included. Tenant-scoped. Returns aggregates only — no PII (no employee data, no wages, no notice subjects).
Aggregate compliance-obligations rollup across the CPA's employer book. Paginated via `limit` (1-100, default 25) and zero-indexed `offset`; optional case-insensitive `search` on legal_name. For each employer: applicable / low-confidence / pending-data obligation counts plus the earliest computable due date; firm-wide totals included. Tenant-scoped. Returns aggregates only — no PII (no employee data, no wages, no notice subjects).
employers.readRequest body CpaSummarizeObligationsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
limit | integer | optional | — |
offset | integer | optional | — |
search | string | optional | — |
Responses
- 200Successful invocation
CpaSummarizeObligationsOutputField Type Required Description per_entityarray<object>required — totalsobjectrequired — totalintegerrequired — generated_atstringrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `employers.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:cpa-team-invitations6
POST/v1/cpa-team-invitations.createInvitationCreate a firm team-invitation row, generate a single-use accept token, and email it to the invitee. The accept link is never returned to the caller (ruling 97(a)); the response reports whether the email was sent. Only firm_admin/system_admin may call.
Create a firm team-invitation row, generate a single-use accept token, and email it to the invitee. The accept link is never returned to the caller (ruling 97(a)); the response reports whether the email was sent. Only firm_admin/system_admin may call.
admin.writeRequest body CpaTeamInvitationsCreateInvitationInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
email | string · email | required | — |
invited_role | enum: "cpa_member" | "staff" | required | — |
Responses
- 200Successful invocation
CpaTeamInvitationsCreateInvitationOutputField Type Required Description invitationobjectrequired — email_sentbooleanrequired — email_failure_reasonenum: "suppressed_non_prod" | "rate_limited" | "rejected" | "unavailable" | …optional — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/cpa-team-invitations.listInvitationsList firm team invitations for the caller's tenant. Never returns plaintext tokens or token hashes.
List firm team invitations for the caller's tenant. Never returns plaintext tokens or token hashes.
admin.readRequest body CpaTeamInvitationsListInvitationsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
status | enum: "pending" | "accepted" | "revoked" | "expired" | optional | — |
Responses
- 200Successful invocation
CpaTeamInvitationsListInvitationsOutputField Type Required Description invitationsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/cpa-team-invitations.revokeInvitationRevoke a pending firm invitation. Idempotent for already-revoked/accepted rows (returns current status). Only firm_admin/system_admin may revoke.
Revoke a pending firm invitation. Idempotent for already-revoked/accepted rows (returns current status). Only firm_admin/system_admin may revoke.
admin.writeRequest body CpaTeamInvitationsRevokeInvitationInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
invitation_id | string | required | — |
Responses
- 200Successful invocation
CpaTeamInvitationsRevokeInvitationOutputField Type Required Description idstringrequired — statusstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/cpa-team-invitations.resendInvitationRe-send a pending firm invitation's e-mail. Mints a NEW single-use accept token and invalidates the previous one, so the earlier link stops working. The accept link is never returned to the caller (ruling 97(a)); the response reports whether the e-mail was sent. Expired, accepted and revoked invitations cannot be resent. Only firm_admin/system_admin may call.
Re-send a pending firm invitation's e-mail. Mints a NEW single-use accept token and invalidates the previous one, so the earlier link stops working. The accept link is never returned to the caller (ruling 97(a)); the response reports whether the e-mail was sent. Expired, accepted and revoked invitations cannot be resent. Only firm_admin/system_admin may call.
admin.writeRequest body CpaTeamInvitationsResendInvitationInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
invitation_id | string | required | — |
Responses
- 200Successful invocation
CpaTeamInvitationsResendInvitationOutputField Type Required Description idstringrequired — statusstringrequired — expires_atstringrequired — email_sentbooleanrequired — email_failure_reasonenum: "suppressed_non_prod" | "rate_limited" | "rejected" | "unavailable" | …optional — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/cpa-team-invitations.acceptInvitationAccept a firm team invitation. Verifies token, expiry, self-acceptance and email-mismatch guards. Grants user_profiles.role + a portal_roles row on success. The caller MUST be signed in as the invited email.
Accept a firm team invitation. Verifies token, expiry, self-acceptance and email-mismatch guards. Grants user_profiles.role + a portal_roles row on success. The caller MUST be signed in as the invited email.
admin.writeRequest body CpaTeamInvitationsAcceptInvitationInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
token | string | required | — |
Responses
- 200Successful invocation
CpaTeamInvitationsAcceptInvitationOutputField Type Required Description invitation_idstringrequired — invited_rolestringrequired — current_rolestringrequired — role_grantedbooleanrequired — existing_role_preservedbooleanrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/cpa-team-invitations.getInvitationByTokenPublic lookup by plaintext token (hashes on read; never returns hash or plaintext). Used by /accept-cpa-invite/[token] for render.
Public lookup by plaintext token (hashes on read; never returns hash or plaintext). Used by /accept-cpa-invite/[token] for render.
admin.readRequest body CpaTeamInvitationsGetInvitationByTokenInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
token | string | required | — |
Responses
- 200Successful invocation
CpaTeamInvitationsGetInvitationByTokenOutputField Type Required Description invitationobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:credential-vault15
POST/v1/credential-vault.createCredentialCreate a tax account credential. Plaintext password / mfaSecret fields are written to Supabase Vault (vault.secrets) and the returned secret IDs are stored on tax_account_credentials. APPROVAL_REQUIRED — agents must not invoke this without HITL.
Create a tax account credential. Plaintext password / mfaSecret fields are written to Supabase Vault (vault.secrets) and the returned secret IDs are stored on tax_account_credentials. APPROVAL_REQUIRED — agents must not invoke this without HITL.
admin.writeRequest body CredentialVaultCreateCredentialInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
employerEntityId | string | required | — |
agencyId | string | required | — |
accountNumber | string | optional | — |
loginUsername | string | optional | — |
password | string | optional | — |
mfaSecret | string | optional | — |
backupCodes | string | optional | — |
notes | string | optional | — |
otpChannel | enum: "none" | "totp" | "email" | "sms" | optional | — |
Responses
- 200Successful invocation
CredentialVaultCreateCredentialOutputField Type Required Description idstringrequired — tenant_idstringrequired — employer_entity_idstringrequired — agency_idstringrequired — account_numberstringrequired — login_usernamestringrequired — has_passwordbooleanrequired — has_mfa_secretbooleanrequired — has_backup_codesbooleanrequired — last_login_atstringrequired — last_password_change_atstringrequired — refresh_required_bystringrequired — statusenum: "active" | "revoked" | "refresh_required" | "locked"required — notesstringrequired — created_atstringrequired — updated_atstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/credential-vault.getCredentialFetch a credential by id, including plaintext password and mfaSecret decrypted from Supabase Vault. APPROVAL_REQUIRED. The returned plaintext fields are named `password` / `mfaSecret` (camelCase, matching framework SENSITIVE_KEYS) so the audit row's outputs JSONB shows them as [redacted] while the live caller receives the actual plaintext (R13 mitigation).
Fetch a credential by id, including plaintext password and mfaSecret decrypted from Supabase Vault. APPROVAL_REQUIRED. The returned plaintext fields are named `password` / `mfaSecret` (camelCase, matching framework SENSITIVE_KEYS) so the audit row's outputs JSONB shows them as [redacted] while the live caller receives the actual plaintext (R13 mitigation).
admin.writeRequest body CredentialVaultGetCredentialInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
credentialId | string | required | — |
purpose | string | required | — |
Responses
- 200Successful invocation
CredentialVaultGetCredentialOutputField Type Required Description idstringrequired — tenant_idstringrequired — employer_entity_idstringrequired — agency_idstringrequired — account_numberstringrequired — login_usernamestringrequired — has_passwordbooleanrequired — has_mfa_secretbooleanrequired — has_backup_codesbooleanrequired — last_login_atstringrequired — last_password_change_atstringrequired — refresh_required_bystringrequired — statusenum: "active" | "revoked" | "refresh_required" | "locked"required — notesstringrequired — created_atstringrequired — updated_atstringrequired — passwordstringrequired — mfaSecretstringrequired — backupCodesstringrequired — health_check_auto_flipbooleanoptional — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/credential-vault.getCredentialByEntityResolve and fetch a credential by (tenantId, entityId, agencyId) lookup, returning plaintext password and mfaSecret. The 3-tuple is index-served by tac_unique_active_per_agency. Throws if no active credential matches. APPROVAL_REQUIRED. Plaintext fields are camelCase-named (`password`, `mfaSecret`) so the audit row's outputs JSONB shows them as [redacted] (R13 mitigation).
Resolve and fetch a credential by (tenantId, entityId, agencyId) lookup, returning plaintext password and mfaSecret. The 3-tuple is index-served by tac_unique_active_per_agency. Throws if no active credential matches. APPROVAL_REQUIRED. Plaintext fields are camelCase-named (`password`, `mfaSecret`) so the audit row's outputs JSONB shows them as [redacted] (R13 mitigation).
admin.writeRequest body CredentialVaultGetCredentialByEntityInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
agencyId | string | required | — |
purpose | string | required | — |
Responses
- 200Successful invocation
CredentialVaultGetCredentialByEntityOutputField Type Required Description idstringrequired — tenant_idstringrequired — employer_entity_idstringrequired — agency_idstringrequired — account_numberstringrequired — login_usernamestringrequired — has_passwordbooleanrequired — has_mfa_secretbooleanrequired — has_backup_codesbooleanrequired — last_login_atstringrequired — last_password_change_atstringrequired — refresh_required_bystringrequired — statusenum: "active" | "revoked" | "refresh_required" | "locked"required — notesstringrequired — created_atstringrequired — updated_atstringrequired — passwordstringrequired — mfaSecretstringrequired — backupCodesstringrequired — health_check_auto_flipbooleanoptional — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/credential-vault.listCredentialsList credentials in the caller's tenant. Returns metadata only (no plaintext, no vault secret_ids). AUTONOMOUS — agents may call freely. Pass `entityId` to scope to a single employer; pass `statuses` to widen beyond the default `active` filter.
List credentials in the caller's tenant. Returns metadata only (no plaintext, no vault secret_ids). AUTONOMOUS — agents may call freely. Pass `entityId` to scope to a single employer; pass `statuses` to widen beyond the default `active` filter.
admin.readRequest body CredentialVaultListCredentialsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | optional | — |
statuses | array<enum: "active" | "revoked" | "refresh_required" | "locked"> | optional | — |
Responses
- 200Successful invocation
CredentialVaultListCredentialsOutputField Type Required Description credentialsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/credential-vault.listCredentialsExpiringSoonList active credentials whose refresh_required_by falls within the next `daysAhead` days. Used by the auto-refresh job (plan §6 D4). AUTONOMOUS.
List active credentials whose refresh_required_by falls within the next `daysAhead` days. Used by the auto-refresh job (plan §6 D4). AUTONOMOUS.
admin.readRequest body CredentialVaultListCredentialsExpiringSoonInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
daysAhead | integer | required | — |
Responses
- 200Successful invocation
CredentialVaultListCredentialsExpiringSoonOutputField Type Required Description credentialsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/credential-vault.updateCredentialUpdate credential metadata and/or rotate plaintext password / mfaSecret. APPROVAL_REQUIRED. Plaintext rotations go through vault.update_secret; the secret_id on the credential row is preserved so audit trails remain stable.
Update credential metadata and/or rotate plaintext password / mfaSecret. APPROVAL_REQUIRED. Plaintext rotations go through vault.update_secret; the secret_id on the credential row is preserved so audit trails remain stable.
admin.writeRequest body CredentialVaultUpdateCredentialInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
credentialId | string | required | — |
patch | object | required | — |
Responses
- 200Successful invocation
CredentialVaultUpdateCredentialOutputField Type Required Description idstringrequired — tenant_idstringrequired — employer_entity_idstringrequired — agency_idstringrequired — account_numberstringrequired — login_usernamestringrequired — has_passwordbooleanrequired — has_mfa_secretbooleanrequired — has_backup_codesbooleanrequired — last_login_atstringrequired — last_password_change_atstringrequired — refresh_required_bystringrequired — statusenum: "active" | "revoked" | "refresh_required" | "locked"required — notesstringrequired — created_atstringrequired — updated_atstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/credential-vault.revokeCredentialSet status='revoked' on a credential AND overwrite the plaintext in Supabase Vault with a tombstone marker. The vault secret_ids stay on the credential row so the audit trail can resolve which credential a historical event referenced, but the actual plaintext is unrecoverable after revoke. Aligns with GDPR Art. 17 / CCPA right-to-erasure. APPROVAL_REQUIRED. PR #466 review Bug 4.
Set status='revoked' on a credential AND overwrite the plaintext in Supabase Vault with a tombstone marker. The vault secret_ids stay on the credential row so the audit trail can resolve which credential a historical event referenced, but the actual plaintext is unrecoverable after revoke. Aligns with GDPR Art. 17 / CCPA right-to-erasure. APPROVAL_REQUIRED. PR #466 review Bug 4.
admin.writeRequest body CredentialVaultRevokeCredentialInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
credentialId | string | required | — |
Responses
- 200Successful invocation
CredentialVaultRevokeCredentialOutputField Type Required Description idstringrequired — tenant_idstringrequired — employer_entity_idstringrequired — agency_idstringrequired — account_numberstringrequired — login_usernamestringrequired — has_passwordbooleanrequired — has_mfa_secretbooleanrequired — has_backup_codesbooleanrequired — last_login_atstringrequired — last_password_change_atstringrequired — refresh_required_bystringrequired — statusenum: "active" | "revoked" | "refresh_required" | "locked"required — notesstringrequired — created_atstringrequired — updated_atstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/credential-vault.markRefreshRequiredFlip a credential to status='refresh_required' and set refresh_required_by=now(). Lighter-risk than other write methods because no plaintext is touched — just state. NOTIFY.
Flip a credential to status='refresh_required' and set refresh_required_by=now(). Lighter-risk than other write methods because no plaintext is touched — just state. NOTIFY.
admin.writeRequest body CredentialVaultMarkRefreshRequiredInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
credentialId | string | required | — |
Responses
- 200Successful invocation
CredentialVaultMarkRefreshRequiredOutputField Type Required Description idstringrequired — tenant_idstringrequired — employer_entity_idstringrequired — agency_idstringrequired — account_numberstringrequired — login_usernamestringrequired — has_passwordbooleanrequired — has_mfa_secretbooleanrequired — has_backup_codesbooleanrequired — last_login_atstringrequired — last_password_change_atstringrequired — refresh_required_bystringrequired — statusenum: "active" | "revoked" | "refresh_required" | "locked"required — notesstringrequired — created_atstringrequired — updated_atstringrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/credential-vault.getCurrentOtpReturn the current RFC-6238 TOTP code for a credential's stored authenticator (MFA) seed, plus seconds remaining in the period. Returns { code: null, secondsRemaining: 0 } when the credential has no stored MFA seed. APPROVAL_REQUIRED — it reads decrypted credential auth material from the vault.
Return the current RFC-6238 TOTP code for a credential's stored authenticator (MFA) seed, plus seconds remaining in the period. Returns { code: null, secondsRemaining: 0 } when the credential has no stored MFA seed. APPROVAL_REQUIRED — it reads decrypted credential auth material from the vault.
admin.writeRequest body CredentialVaultGetCurrentOtpInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
credentialId | string | required | — |
Responses
- 200Successful invocation
CredentialVaultGetCurrentOtpOutputField Type Required Description codestringrequired — secondsRemainingintegerrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/credential-vault.getHealthCheckPolicyRead the per-agency health-check status-mutation policy for the caller's tenant. AUTONOMOUS — read-only config. Returns statusMutationEnabled=false (record-only, the safe default) when no policy row exists for the agency.
Read the per-agency health-check status-mutation policy for the caller's tenant. AUTONOMOUS — read-only config. Returns statusMutationEnabled=false (record-only, the safe default) when no policy row exists for the agency.
admin.readRequest body CredentialVaultGetHealthCheckPolicyInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
agencyId | string | required | — |
Responses
- 200Successful invocation
CredentialVaultGetHealthCheckPolicyOutputField Type Required Description agencyIdstringrequired — statusMutationEnabledbooleanrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/credential-vault.setHealthCheckPolicyEnable/disable health-check status auto-flip for one agency in the caller's tenant. NOTIFY — a config write that should be human-visible. Enable only for portals whose invalidSignal coverage is proven (un-hardened portals stay record-only). Upserts the per-(tenant, agency) policy row.
Enable/disable health-check status auto-flip for one agency in the caller's tenant. NOTIFY — a config write that should be human-visible. Enable only for portals whose invalidSignal coverage is proven (un-hardened portals stay record-only). Upserts the per-(tenant, agency) policy row.
admin.writeRequest body CredentialVaultSetHealthCheckPolicyInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
agencyId | string | required | — |
statusMutationEnabled | boolean | required | — |
Responses
- 200Successful invocation
CredentialVaultSetHealthCheckPolicyOutputField Type Required Description agencyIdstringrequired — statusMutationEnabledbooleanrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/credential-vault.useUse a stored credential BY REFERENCE to drive an authenticated portal login: dispatches the portal-execution job and returns the job id only. The plaintext secret is decrypted exclusively inside the portal worker and never appears in this method's output. Use when a filing or protest flow needs an authenticated portal session.
Use a stored credential BY REFERENCE to drive an authenticated portal login: dispatches the portal-execution job and returns the job id only. The plaintext secret is decrypted exclusively inside the portal worker and never appears in this method's output. Use when a filing or protest flow needs an authenticated portal session.
admin.readRequest body CredentialVaultUseInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
credentialId | string | required | — |
purpose | string | required | — |
dedupeToken | string | required | — |
Responses
- 200Successful invocation
CredentialVaultUseOutputField Type Required Description jobIdstringrequired — deduplicatedbooleanrequired — credentialIdstringrequired — agencyIdstringrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/credential-vault.rotateRotate a stored portal password BY REFERENCE: dispatches the transactional rotation job (login, change-password, verify-login in one governed session) and returns the job id only. The new secret is generated inside the worker and never appears anywhere outside the vault and the portal DOM. Use when a credential is suspected compromised or a portal's password-age policy requires a change.
Rotate a stored portal password BY REFERENCE: dispatches the transactional rotation job (login, change-password, verify-login in one governed session) and returns the job id only. The new secret is generated inside the worker and never appears anywhere outside the vault and the portal DOM. Use when a credential is suspected compromised or a portal's password-age policy requires a change.
admin.writeRequest body CredentialVaultRotateInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
credentialId | string | required | — |
reason | string | optional | — |
Responses
- 200Successful invocation
CredentialVaultRotateOutputField Type Required Description jobIdstringrequired — deduplicatedbooleanrequired — credentialIdstringrequired — agencyIdstringrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/credential-vault.healthReport a credential's login health from the latest lockout-aware scheduled probe (the hourly health cron is the only thing that actually logs in — this read never touches the portal, so it can never contribute to a lockout). Use to surface which credentials need attention before relying on one for a filing. 'uncertain' means no probe evidence — never treat it as healthy.
Report a credential's login health from the latest lockout-aware scheduled probe (the hourly health cron is the only thing that actually logs in — this read never touches the portal, so it can never contribute to a lockout). Use to surface which credentials need attention before relying on one for a filing. 'uncertain' means no probe evidence — never treat it as healthy.
admin.readRequest body CredentialVaultHealthInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
credentialId | string | required | — |
Responses
- 200Successful invocation
CredentialVaultHealthOutputField Type Required Description credentialIdstringrequired — statusstringrequired — healthenum: "pass" | "fail" | "uncertain"required — lastHealthCheckAtstringrequired — lastHealthCheckOutcomestringrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/credential-vault.revealReveal a credential's plaintext to an authorized human screen. Requires a typed reason and passes the account's reveal-visibility policy for VAULT_V1 tenants; the hash-chained evidence row is written BEFORE decryption and the reveal is refused if it cannot be. APPROVAL_REQUIRED — UI-only, never exposed via MCP or CLI.
Reveal a credential's plaintext to an authorized human screen. Requires a typed reason and passes the account's reveal-visibility policy for VAULT_V1 tenants; the hash-chained evidence row is written BEFORE decryption and the reveal is refused if it cannot be. APPROVAL_REQUIRED — UI-only, never exposed via MCP or CLI.
admin.writeRequest body CredentialVaultRevealInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
credentialId | string | required | — |
reason | string | optional | — |
ipAddress | string | optional | — |
userAgent | string | optional | — |
Responses
- 200Successful invocation
CredentialVaultRevealOutputField Type Required Description idstringrequired — tenant_idstringrequired — employer_entity_idstringrequired — agency_idstringrequired — account_numberstringrequired — login_usernamestringrequired — has_passwordbooleanrequired — has_mfa_secretbooleanrequired — has_backup_codesbooleanrequired — last_login_atstringrequired — last_password_change_atstringrequired — refresh_required_bystringrequired — statusenum: "active" | "revoked" | "refresh_required" | "locked"required — notesstringrequired — created_atstringrequired — updated_atstringrequired — passwordstringrequired — mfaSecretstringrequired — backupCodesstringrequired — health_check_auto_flipbooleanoptional — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:documents2
POST/v1/documents.listSigningRequestsList a notice's LIVE e-signature requests, newest first: pending requests that have not expired (on a live document) and signed ones. Returns ids, status and dates only — never the signer's name, email or the signing link. Use an id here with documents.cancelSigningRequest.
List a notice's LIVE e-signature requests, newest first: pending requests that have not expired (on a live document) and signed ones. Returns ids, status and dates only — never the signer's name, email or the signing link. Use an id here with documents.cancelSigningRequest.
notices.readRequest body DocumentsListSigningRequestsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string | required | — |
Responses
- 200Successful invocation
DocumentsListSigningRequestsOutputField Type Required Description requestsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/documents.cancelSigningRequestWithdraw one PENDING, unexpired e-signature request in the caller's firm. The signing link stops working at once (it then answers exactly like an expired or unknown link), and the notice's signature-requested fact is re-derived — cleared when no other request holds it, so the draft can be regenerated or revised again. A signed, expired or already cancelled request is refused (409, with the reason). Recoverable by sending a new request.
Withdraw one PENDING, unexpired e-signature request in the caller's firm. The signing link stops working at once (it then answers exactly like an expired or unknown link), and the notice's signature-requested fact is re-derived — cleared when no other request holds it, so the draft can be regenerated or revised again. A signed, expired or already cancelled request is refused (409, with the reason). Recoverable by sending a new request.
notices.writeRequest body DocumentsCancelSigningRequestInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
requestId | string | required | — |
Responses
- 200Successful invocation
DocumentsCancelSigningRequestOutputField Type Required Description okenum: truerequired — requestIdstringrequired — statusenum: "cancelled"required — noticeIdstringrequired — signatureRequestedAtstringrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:edelivery10
POST/v1/edelivery.listEdeliveryRulesBrowse the e-delivery catalog (state_edelivery_rules, global config — NO tenant filter). Optional filters: stateCode, requirementType, verificationStatus. Returns matching rows plus HONEST per-status counts ({unverified, human_reviewed, verified}) — never a 'done/complete' claim. AUTONOMOUS, read-only.
Browse the e-delivery catalog (state_edelivery_rules, global config — NO tenant filter). Optional filters: stateCode, requirementType, verificationStatus. Returns matching rows plus HONEST per-status counts ({unverified, human_reviewed, verified}) — never a 'done/complete' claim. AUTONOMOUS, read-only.
notices.readRequest body EdeliveryListEdeliveryRulesInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | optional | — |
requirementType | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | optional | — |
verificationStatus | enum: "unverified" | "human_reviewed" | "verified" | optional | — |
Responses
- 200Successful invocation
EdeliveryListEdeliveryRulesOutputField Type Required Description rowsarray<object>required — countsobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/edelivery.getEdeliveryRuleRead one e-delivery catalog cell (state_edelivery_rules, global config — NO tenant filter): the rule row plus its typed ordered enrollment steps. Cell absent → honest null + [] (never invented data). AUTONOMOUS, read-only.
Read one e-delivery catalog cell (state_edelivery_rules, global config — NO tenant filter): the rule row plus its typed ordered enrollment steps. Cell absent → honest null + [] (never invented data). AUTONOMOUS, read-only.
notices.readRequest body EdeliveryGetEdeliveryRuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
requirementType | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | required | — |
Responses
- 200Successful invocation
EdeliveryGetEdeliveryRuleOutputField Type Required Description ruleobjectrequired — stepsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/edelivery.proposeEdeliveryRuleResearch one (state, requirement) cell's PAPERLESS/e-delivery facts via the Claude + web_search agent and upsert an UNVERIFIED proposal onto state_edelivery_rules BY PK (global config — NO tenant filter). The write ALWAYS leaves verification_status='unverified' and NEVER sets verified/human_reviewed (agents may never verify); REFUSES to write at all unless the proposal carries complete provenance (source_url + retrieved_at + a verbatim quote + an evidence tier); idempotent-by-PK. MUST NOT downgrade a human-verified cell: returns the existing row + the fresh proposal so a human can compare. PII-free. NOTIFY: agents may call; a human reviews before promotion.
Research one (state, requirement) cell's PAPERLESS/e-delivery facts via the Claude + web_search agent and upsert an UNVERIFIED proposal onto state_edelivery_rules BY PK (global config — NO tenant filter). The write ALWAYS leaves verification_status='unverified' and NEVER sets verified/human_reviewed (agents may never verify); REFUSES to write at all unless the proposal carries complete provenance (source_url + retrieved_at + a verbatim quote + an evidence tier); idempotent-by-PK. MUST NOT downgrade a human-verified cell: returns the existing row + the fresh proposal so a human can compare. PII-free. NOTIFY: agents may call; a human reviews before promotion.
notices.writeRequest body EdeliveryProposeEdeliveryRuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
requirementType | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | required | — |
Responses
- 200Successful invocation
EdeliveryProposeEdeliveryRuleOutputField Type Required Description rowobjectrequired — proposalobjectrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/edelivery.promoteEdeliveryRuleHuman gate: promote an e-delivery cell ONE RUNG along the verification ladder (unverified → human_reviewed → verified) — a transition that skips a rung, or runs backwards, is refused. APPROVAL_REQUIRED, UI-only — excluded from MCP/CLI, and the handler REJECTS any non-human principal before any write. Verifying requires provenance (source_url + retrieved_at). Stamps reviewed_by / last_reviewed_at, and on verify also verified_at / verified_by.
Human gate: promote an e-delivery cell ONE RUNG along the verification ladder (unverified → human_reviewed → verified) — a transition that skips a rung, or runs backwards, is refused. APPROVAL_REQUIRED, UI-only — excluded from MCP/CLI, and the handler REJECTS any non-human principal before any write. Verifying requires provenance (source_url + retrieved_at). Stamps reviewed_by / last_reviewed_at, and on verify also verified_at / verified_by.
notices.writeRequest body EdeliveryPromoteEdeliveryRuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
requirementType | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | required | — |
toStatus | enum: "human_reviewed" | "verified" | required | — |
reason | string | optional | — |
Responses
- 200Successful invocation
EdeliveryPromoteEdeliveryRuleOutputField Type Required Description stateCodestringrequired — requirementTypeenum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | …required — fromStatusenum: "unverified" | "human_reviewed" | "verified"required — toStatusenum: "unverified" | "human_reviewed" | "verified"required — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden. `SYSTEM_ADMIN_REQUIRED`: the caller is not a Kreto system administrator on an interactive session — the global catalog every firm pins (handbook modules, state filing / foreign-qualification / annual-report / e-delivery rules) is staff-only, exactly as on the REST admin door. Checked before any catalog read, so the answer is the same whether or not the row exists. `STEP_UP_REQUIRED`: the dispatcher's APPROVAL_REQUIRED gate (an API key, or no fresh re-authentication). Nothing is written.
one of (L2SystemAdminRequiredRefusal · L2ErrorEnvelope) - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/edelivery.listEdeliveryEnrollmentsList the per-entity e-delivery enrollment checklist (tenant+entity scoped), idempotently seeding missing (state, requirement) items from the employer's footprint (employer_state_registrations + states_operated; v1 scope = SUI + withholding). Each item carries its catalog cell's facts FAIL-CLOSED: enrollment steps are attached ONLY when the cell is verified — unverified facts never render as authoritative. AUTONOMOUS, read-mostly.
List the per-entity e-delivery enrollment checklist (tenant+entity scoped), idempotently seeding missing (state, requirement) items from the employer's footprint (employer_state_registrations + states_operated; v1 scope = SUI + withholding). Each item carries its catalog cell's facts FAIL-CLOSED: enrollment steps are attached ONLY when the cell is verified — unverified facts never render as authoritative. AUTONOMOUS, read-mostly.
notices.readRequest body EdeliveryListEdeliveryEnrollmentsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
Responses
- 200Successful invocation
EdeliveryListEdeliveryEnrollmentsOutputField Type Required Description entityIdstringrequired — intakeEmailstringrequired — forwardingOnbooleanrequired — itemsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/edelivery.listEdeliveryEnrollmentsReadonlyRead an entity's e-delivery enrollment checklist WITHOUT seeding (the employer-portal spectator view). Returns the persisted items the CPA/Kreto-ops maintains, each with its catalog facts FAIL-CLOSED: authoritative steps only on verified cells, unverified steps under `draftSteps` for the 'unverified draft' banner. AUTONOMOUS, read-only.
Read an entity's e-delivery enrollment checklist WITHOUT seeding (the employer-portal spectator view). Returns the persisted items the CPA/Kreto-ops maintains, each with its catalog facts FAIL-CLOSED: authoritative steps only on verified cells, unverified steps under `draftSteps` for the 'unverified draft' banner. AUTONOMOUS, read-only.
notices.readRequest body EdeliveryListEdeliveryEnrollmentsReadonlyInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
Responses
- 200Successful invocation
EdeliveryListEdeliveryEnrollmentsReadonlyOutputField Type Required Description entityIdstringrequired — intakeEmailstringrequired — forwardingOnbooleanrequired — itemsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/edelivery.setEnrollmentStatusSet one e-delivery checklist item's status (not_started | enrolled) with human attribution (completed_by = the session user on enroll). Tenant+entity scoped. Appends a hash-chained audit_events row — enrollment status is a compliance-posture claim. NOTIFY. The customer performed the enrollment in the agency portal; Kreto only records it.
Set one e-delivery checklist item's status (not_started | enrolled) with human attribution (completed_by = the session user on enroll). Tenant+entity scoped. Appends a hash-chained audit_events row — enrollment status is a compliance-posture claim. NOTIFY. The customer performed the enrollment in the agency portal; Kreto only records it.
notices.writeRequest body EdeliverySetEnrollmentStatusInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
itemId | string | optional | — |
stateCode | string | optional | — |
requirementType | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | optional | — |
status | enum: "not_started" | "enrolled" | required | — |
Responses
- 200Successful invocation
EdeliverySetEnrollmentStatusOutputField Type Required Description itemIdstringrequired — stateCodestringrequired — requirementTypeenum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | …required — statusenum: "not_started" | "enrolled"required — completedAtstringrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/edelivery.listFetchQueueList the tenant's OPEN Portal-pickup tasks: inbound agency notification emails ('you have new correspondence — log in') that carried no document (inbound_emails processing_status='notification_only', unresolved). Optional entityId filter. The row IS the task — a notice is only ever created when the fetched document arrives. AUTONOMOUS, read-only.
List the tenant's OPEN Portal-pickup tasks: inbound agency notification emails ('you have new correspondence — log in') that carried no document (inbound_emails processing_status='notification_only', unresolved). Optional entityId filter. The row IS the task — a notice is only ever created when the fetched document arrives. AUTONOMOUS, read-only.
notices.readRequest body EdeliveryListFetchQueueInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | optional | — |
Responses
- 200Successful invocation
EdeliveryListFetchQueueOutputField Type Required Description tasksarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/edelivery.resolveFetchTaskResolve one Portal-pickup task (inbound_emails processing_status='notification_only'): stamps resolved_at/resolved_by and optionally back-links the notice created when the customer fetched the document (the noticeId must belong to the caller's tenant). Tenant-scoped. NOTIFY.
Resolve one Portal-pickup task (inbound_emails processing_status='notification_only'): stamps resolved_at/resolved_by and optionally back-links the notice created when the customer fetched the document (the noticeId must belong to the caller's tenant). Tenant-scoped. NOTIFY.
notices.writeRequest body EdeliveryResolveFetchTaskInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
taskId | string | required | — |
noticeId | string | optional | — |
Responses
- 200Successful invocation
EdeliveryResolveFetchTaskOutputField Type Required Description taskIdstringrequired — resolvedAtstringrequired — noticeIdstringrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/edelivery.enrollViaKretoOpsKreto ops performs an agency-portal paperless enrollment ON THE CUSTOMER'S BEHALF under an explicit consent artifact + stored portal credentials. APPROVAL_REQUIRED, UI-only — excluded from MCP/CLI, and the handler REJECTS any non-human principal before any write. Records an IMMUTABLE per-enrollment consent (who consented, when, IP/UA, scope) and flips the checklist item to enrolled, attributed to ops. HONESTY: customer-facing copy stays 'the customer performs' until this flow is enabled + verified.
Kreto ops performs an agency-portal paperless enrollment ON THE CUSTOMER'S BEHALF under an explicit consent artifact + stored portal credentials. APPROVAL_REQUIRED, UI-only — excluded from MCP/CLI, and the handler REJECTS any non-human principal before any write. Records an IMMUTABLE per-enrollment consent (who consented, when, IP/UA, scope) and flips the checklist item to enrolled, attributed to ops. HONESTY: customer-facing copy stays 'the customer performs' until this flow is enabled + verified.
notices.writeRequest body EdeliveryEnrollViaKretoOpsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
stateCode | string | required | — |
requirementType | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | required | — |
consentedByUserId | string | required | — |
consentIp | string | optional | — |
consentUserAgent | string | optional | — |
Responses
- 200Successful invocation
EdeliveryEnrollViaKretoOpsOutputField Type Required Description consentIdstringrequired — itemIdstringrequired — stateCodestringrequired — requirementTypeenum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | …required — statusenum: "not_started" | "enrolled"required — enrolledViaOpsbooleanrequired — reusedExistingConsentbooleanrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:employee-roster6
POST/v1/employee-roster.getActiveRosterList employees on the active roster for the caller's tenant. Defaults to employment_status='active'; pass `statuses` to widen (e.g. ['active','terminated','leave'] for the I-9 list). Pass `entityIds` to narrow to a subset of entities the caller has access to. If `asOf` is supplied, joins payroll_pay_periods to detect employees who had a paycheck within 60 days of asOf and weren't terminated before it.
List employees on the active roster for the caller's tenant. Defaults to employment_status='active'; pass `statuses` to widen (e.g. ['active','terminated','leave'] for the I-9 list). Pass `entityIds` to narrow to a subset of entities the caller has access to. If `asOf` is supplied, joins payroll_pay_periods to detect employees who had a paycheck within 60 days of asOf and weren't terminated before it.
payroll.readRequest body EmployeeRosterGetActiveRosterInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
asOf | string · date-time | optional | — |
entityIds | array<string> | optional | — |
statuses | array<enum: "active" | "terminated" | "leave"> | optional | — |
Responses
- 200Successful invocation
EmployeeRosterGetActiveRosterOutputField Type Required Description employeesarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/employee-roster.getEmployeeFetch a single employee by id, scoped to the caller's tenant. Returns null if no row matches. Per README §6 Q5, no asOf is supported on this method; per-employee historical context comes from payroll_pay_periods joins, not effective-dating columns.
Fetch a single employee by id, scoped to the caller's tenant. Returns null if no row matches. Per README §6 Q5, no asOf is supported on this method; per-employee historical context comes from payroll_pay_periods joins, not effective-dating columns.
payroll.readRequest body EmployeeRosterGetEmployeeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
employeeId | string | required | — |
Responses
- 200Successful invocation
EmployeeRosterGetEmployeeOutputField Type Required Description idstringrequired — entity_idstringrequired — ssn_last4stringrequired — full_namestringrequired — emailstringrequired — departmentstringrequired — hire_datestringrequired — termination_datestringrequired — employment_statusenum: "active" | "terminated" | "leave"required — work_statestringrequired — home_statestringrequired — salary_typeenum: "hourly" | "salary" | "unknown"required — hourly_ratenumberrequired — annual_salarynumberrequired — i9_section1_completebooleanrequired — i9_section2_completebooleanrequired — i9_reverification_neededbooleanrequired — work_auth_typestringrequired — work_auth_expirystringrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/employee-roster.getEmployeesByStateList employees whose canonical work_state matches the supplied two-letter code (e.g. 'OH'). asOf is optional; without it, the active roster filtered by work_state is returned. With asOf, the asOf logic from getActiveRoster applies first, then the work_state filter narrows the result.
List employees whose canonical work_state matches the supplied two-letter code (e.g. 'OH'). asOf is optional; without it, the active roster filtered by work_state is returned. With asOf, the asOf logic from getActiveRoster applies first, then the work_state filter narrows the result.
payroll.readRequest body EmployeeRosterGetEmployeesByStateInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
state | string | required | — |
asOf | string · date-time | optional | — |
Responses
- 200Successful invocation
EmployeeRosterGetEmployeesByStateOutputField Type Required Description employeesarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/employee-roster.countEmployeesCount active employees on the caller's tenant. Pass `entityId` to scope to a single employer (PEPM Source 3 pattern). Replaces the ad-hoc count query in src/lib/billing/pepm-quantity.ts — migrated in Part F (KRT-1159). asOf is supported via the same join-with-payroll_pay_periods logic getActiveRoster uses.
Count active employees on the caller's tenant. Pass `entityId` to scope to a single employer (PEPM Source 3 pattern). Replaces the ad-hoc count query in src/lib/billing/pepm-quantity.ts — migrated in Part F (KRT-1159). asOf is supported via the same join-with-payroll_pay_periods logic getActiveRoster uses.
payroll.readRequest body EmployeeRosterCountEmployeesInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
asOf | string · date-time | optional | — |
entityId | string | optional | — |
Responses
- 200Successful invocation
EmployeeRosterCountEmployeesOutputField Type Required Description countintegerrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/employee-roster.getStateChangesDetect work_state transitions across employees by walking payroll_pay_periods paycheck history since the supplied date. Returns one StateChangeEvent per detected transition (e.g. an employee moving MA→KS mid-quarter). The trigger source for the Phase 1+ state-registration agent.
Detect work_state transitions across employees by walking payroll_pay_periods paycheck history since the supplied date. Returns one StateChangeEvent per detected transition (e.g. an employee moving MA→KS mid-quarter). The trigger source for the Phase 1+ state-registration agent.
payroll.readRequest body EmployeeRosterGetStateChangesInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
since | string · date-time | required | — |
Responses
- 200Successful invocation
EmployeeRosterGetStateChangesOutputField Type Required Description changesarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/employee-roster.getRosterWithSsnHashRoster rows for a single entity, with `ssn_hash` exposed in addition to the public EmployeeRosterRow fields. ELEVATED risk: ssn_hash is the cross-payroll-graph join key but is NOT part of the public PII contract. Use only from cross-reference / audit-defense consumers. Filters by employment_status='active' by default; with `asOf`, applies the same payroll_pay_periods liveness heuristic getActiveRoster uses. Excludes rows whose ssn_hash is NULL (those rows can't be joined against the payroll fact graph anyway).
Roster rows for a single entity, with `ssn_hash` exposed in addition to the public EmployeeRosterRow fields. ELEVATED risk: ssn_hash is the cross-payroll-graph join key but is NOT part of the public PII contract. Use only from cross-reference / audit-defense consumers. Filters by employment_status='active' by default; with `asOf`, applies the same payroll_pay_periods liveness heuristic getActiveRoster uses. Excludes rows whose ssn_hash is NULL (those rows can't be joined against the payroll fact graph anyway).
admin.readRequest body EmployeeRosterGetRosterWithSsnHashInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
asOf | string · date-time | optional | — |
Responses
- 200Successful invocation
EmployeeRosterGetRosterWithSsnHashOutputField Type Required Description employeesarray<object>required — - 400Validation error (input schema rejected). risk_level=elevated.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:employers2
POST/v1/employers.setEinStore an employer's FULL federal EIN: plaintext to Supabase Vault, a keyed HMAC to entities.ein_hash for per-firm duplicate refusal, and a server-derived ein_last4. Refuses (409-equivalent) when another live employer in the same tenant already holds that EIN. APPROVAL_REQUIRED and therefore UI-ONLY — deliberately absent from MCP and the CLI until step-up auth (KRT-1554) ships, because this is a PII-ingress write.
Store an employer's FULL federal EIN: plaintext to Supabase Vault, a keyed HMAC to entities.ein_hash for per-firm duplicate refusal, and a server-derived ein_last4. Refuses (409-equivalent) when another live employer in the same tenant already holds that EIN. APPROVAL_REQUIRED and therefore UI-ONLY — deliberately absent from MCP and the CLI until step-up auth (KRT-1554) ships, because this is a PII-ingress write.
employers.writeRequest body EmployersSetEinInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
ein | string | required | — |
Responses
- 200Successful invocation
EmployersSetEinOutputField Type Required Description entityIdstringrequired — einLast4stringrequired — hasFullEinenum: truerequired — replacedbooleanrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `employers.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/employers.einStatusRead-only: does this employer have a FULL EIN on file, and what are its last four digits? Never returns the number, the hash, or the vault pointer — only a boolean and the same four digits already shown on the employer roster. This is the question the 8821 and state-registration readiness flows actually ask.
Read-only: does this employer have a FULL EIN on file, and what are its last four digits? Never returns the number, the hash, or the vault pointer — only a boolean and the same four digits already shown on the employer roster. This is the question the 8821 and state-registration readiness flows actually ask.
employers.readRequest body EmployersEinStatusInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
Responses
- 200Successful invocation
EmployersEinStatusOutputField Type Required Description entityIdstringrequired — hasFullEinbooleanrequired — einLast4stringrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `employers.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:foreign-qualification13
POST/v1/foreign-qualification.listForeignQualRulesList the state foreign-qualification catalog (global config): per-(state, entity_kind) SOS certificate-of-authority rules — agency, form, state filing fee, good-standing prerequisite and doing-business notes — with the verification ladder status of every cell and honest per-status counts. Cells below the display gate (human_reviewed) must never be rendered as authoritative to customers.
List the state foreign-qualification catalog (global config): per-(state, entity_kind) SOS certificate-of-authority rules — agency, form, state filing fee, good-standing prerequisite and doing-business notes — with the verification ladder status of every cell and honest per-status counts. Cells below the display gate (human_reviewed) must never be rendered as authoritative to customers.
foreign_qualifications.readRequest body ForeignQualificationListForeignQualRulesInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | optional | — |
entityKind | enum: "corporation" | "llc" | "nonprofit" | "lp" | … | optional | — |
verificationStatus | enum: "unverified" | "human_reviewed" | "verified" | optional | — |
Responses
- 200Successful invocation
ForeignQualificationListForeignQualRulesOutputField Type Required Description rulesarray<object>required — countsobjectrequired — filteredCountnumberrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `foreign_qualifications.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/foreign-qualification.getForeignQualRuleFetch one foreign-qualification catalog cell with the (state, entity_kind) → (state, 'default') fallback. Null rule = no catalog coverage yet. Advisory context only — doing_business_notes never assert an obligation.
Fetch one foreign-qualification catalog cell with the (state, entity_kind) → (state, 'default') fallback. Null rule = no catalog coverage yet. Advisory context only — doing_business_notes never assert an obligation.
foreign_qualifications.readRequest body ForeignQualificationGetForeignQualRuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
entityKind | enum: "corporation" | "llc" | "nonprofit" | "lp" | … | optional | — |
Responses
- 200Successful invocation
ForeignQualificationGetForeignQualRuleOutputField Type Required Description ruleobjectrequired — usedDefaultFallbackbooleanrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `foreign_qualifications.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/foreign-qualification.proposeForeignQualRuleResearch-agent write path for the foreign-qualification catalog. Upserts one (state, entity_kind) cell as verification_status='unverified' with mandatory provenance (source_url + retrieved_at). REFUSES to touch a cell already at human_reviewed/verified — verified cells are never downgraded (AC-22 clone); corrections go through the review queue.
Research-agent write path for the foreign-qualification catalog. Upserts one (state, entity_kind) cell as verification_status='unverified' with mandatory provenance (source_url + retrieved_at). REFUSES to touch a cell already at human_reviewed/verified — verified cells are never downgraded (AC-22 clone); corrections go through the review queue.
foreign_qualifications.writeRequest body ForeignQualificationProposeForeignQualRuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
entityKind | enum: "corporation" | "llc" | "nonprofit" | "lp" | … | optional | — |
agencyCode | string | required | — |
agencyName | string | required | — |
portalUrl | string · uri | optional | — |
formName | string | optional | — |
filingFeeUsd | number | optional | — |
expediteFeeNotes | string | optional | — |
goodStandingRequired | boolean | optional | — |
goodStandingMaxAgeDays | integer | optional | — |
registeredAgentRequired | boolean | optional | — |
nameCheckNotes | string | optional | — |
doingBusinessNotes | string | optional | — |
penaltyNotes | string | optional | — |
processingTimeNotes | string | optional | — |
sourceUrl | string · uri | required | — |
retrievedAt | string · date-time | required | — |
notes | string | optional | — |
Responses
- 200Successful invocation
ForeignQualificationProposeForeignQualRuleOutputField Type Required Description ruleobjectrequired — createdbooleanrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `foreign_qualifications.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/foreign-qualification.promoteForeignQualRulePromote a foreign-qualification catalog cell along the verification ladder (unverified → human_reviewed → verified). APPROVAL_REQUIRED and UI-only: the handler rejects any non-human principal (AC-20 clone) and refuses cells with no source_url provenance (CWE-345).
Promote a foreign-qualification catalog cell along the verification ladder (unverified → human_reviewed → verified). APPROVAL_REQUIRED and UI-only: the handler rejects any non-human principal (AC-20 clone) and refuses cells with no source_url provenance (CWE-345).
foreign_qualifications.writeRequest body ForeignQualificationPromoteForeignQualRuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
entityKind | enum: "corporation" | "llc" | "nonprofit" | "lp" | … | optional | — |
targetStatus | enum: "human_reviewed" | "verified" | required | — |
reason | string | required | — |
Responses
- 200Successful invocation
ForeignQualificationPromoteForeignQualRuleOutputField Type Required Description ruleobjectrequired — fromStatusenum: "unverified" | "human_reviewed" | "verified"required — toStatusenum: "unverified" | "human_reviewed" | "verified"required — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden. `SYSTEM_ADMIN_REQUIRED`: the caller is not a Kreto system administrator on an interactive session — the global catalog every firm pins (handbook modules, state filing / foreign-qualification / annual-report / e-delivery rules) is staff-only, exactly as on the REST admin door. Checked before any catalog read, so the answer is the same whether or not the row exists. `STEP_UP_REQUIRED`: the dispatcher's APPROVAL_REQUIRED gate (an API key, or no fresh re-authentication). Nothing is written.
one of (L2SystemAdminRequiredRefusal · L2ErrorEnvelope) - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/foreign-qualification.listForeignQualificationsList the tenant's foreign-qualification tracker rows (once per entity × state), filterable by entity, state, status and obligation assessment. Advisory rows are unconfirmed legal judgments — callers must exclude them from compliance/on-track counts (design D5/D6). Paginated (default 25). Tenant-scoped via the invocation context.
List the tenant's foreign-qualification tracker rows (once per entity × state), filterable by entity, state, status and obligation assessment. Advisory rows are unconfirmed legal judgments — callers must exclude them from compliance/on-track counts (design D5/D6). Paginated (default 25). Tenant-scoped via the invocation context.
foreign_qualifications.readRequest body ForeignQualificationListForeignQualificationsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | optional | — |
stateCode | string | optional | — |
status | enum: "pending" | "prepared" | "submitted" | "approved" | … | optional | — |
obligationAssessment | enum: "advisory" | "confirmed" | "not_required" | optional | — |
excludeAdvisory | boolean | optional | — |
limit | integer | optional | — |
offset | integer | optional | — |
Responses
- 200Successful invocation
ForeignQualificationListForeignQualificationsOutputField Type Required Description qualificationsarray<object>required — totalnumberrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `foreign_qualifications.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/foreign-qualification.getForeignQualificationsByEntityFetch every foreign-qualification tracker row for one entity (optionally one state) — advisory, confirmed, dismissed and recorded qualifications. Tenant-scoped.
Fetch every foreign-qualification tracker row for one entity (optionally one state) — advisory, confirmed, dismissed and recorded qualifications. Tenant-scoped.
foreign_qualifications.readRequest body ForeignQualificationGetForeignQualificationsByEntityInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
stateCode | string | optional | — |
Responses
- 200Successful invocation
ForeignQualificationGetForeignQualificationsByEntityOutputField Type Required Description qualificationsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `foreign_qualifications.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/foreign-qualification.detectForeignQualNeedsDry-run compute of an entity's foreign-qualification detection signal (design §4): per candidate state, whether the advisory signal fires (operating evidence outside the formation state with no qualification on record) or an explicit disposition (already_tracked / not_foreign / no_signal / needs_input when the formation state is unknown). Writes NOTHING — the sweep's detection roll persists advisory rows; this method answers 'what would be flagged?'. ADVISORY POSTURE: a firing signal is never an obligation assertion — a human CPA confirms.
Dry-run compute of an entity's foreign-qualification detection signal (design §4): per candidate state, whether the advisory signal fires (operating evidence outside the formation state with no qualification on record) or an explicit disposition (already_tracked / not_foreign / no_signal / needs_input when the formation state is unknown). Writes NOTHING — the sweep's detection roll persists advisory rows; this method answers 'what would be flagged?'. ADVISORY POSTURE: a firing signal is never an obligation assertion — a human CPA confirms.
foreign_qualifications.readRequest body ForeignQualificationDetectForeignQualNeedsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
Responses
- 200Successful invocation
ForeignQualificationDetectForeignQualNeedsOutputField Type Required Description resultobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `foreign_qualifications.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/foreign-qualification.createForeignQualificationCreate one manual foreign-qualification tracker row (source='manual') for an entity × state. Defaults to obligation_assessment='advisory'; creating a CONFIRMED row is a human CPA judgment and rejects agent principals (design D5). Duplicate states are rejected by the once-per-state UNIQUE.
Create one manual foreign-qualification tracker row (source='manual') for an entity × state. Defaults to obligation_assessment='advisory'; creating a CONFIRMED row is a human CPA judgment and rejects agent principals (design D5). Duplicate states are rejected by the once-per-state UNIQUE.
foreign_qualifications.writeRequest body ForeignQualificationCreateForeignQualificationInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
stateCode | string | required | — |
obligationAssessment | enum: "advisory" | "confirmed" | optional | — |
assessmentNote | string | optional | — |
notes | string | optional | — |
Responses
- 200Successful invocation
ForeignQualificationCreateForeignQualificationOutputField Type Required Description qualificationobjectrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `foreign_qualifications.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/foreign-qualification.recordExistingQualificationRecord an ALREADY-HELD foreign qualification (design D12 backfill): the row lands status='approved', obligation_assessment='confirmed', source='manual' with the state-issued qualification_date + sos_file_number. This is the authoritative annual-reports foreign anchor (design D9) and permanently suppresses detection advisories for the state (once-per-state UNIQUE).
Record an ALREADY-HELD foreign qualification (design D12 backfill): the row lands status='approved', obligation_assessment='confirmed', source='manual' with the state-issued qualification_date + sos_file_number. This is the authoritative annual-reports foreign anchor (design D9) and permanently suppresses detection advisories for the state (once-per-state UNIQUE).
foreign_qualifications.writeRequest body ForeignQualificationRecordExistingQualificationInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
stateCode | string | required | — |
qualificationDate | string | required | — |
sosFileNumber | string | optional | — |
notes | string | optional | — |
Responses
- 200Successful invocation
ForeignQualificationRecordExistingQualificationOutputField Type Required Description qualificationobjectrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `foreign_qualifications.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/foreign-qualification.updateForeignQualificationMetadataEdit tracker metadata on one foreign-qualification row: the obligation assessment (confirm the obligation — unlocks prepare — or dismiss as not_required with a REQUIRED assessment note; both are human-only CPA judgments that reject agent principals, design D5), assignee and notes. Writes a structured audit event capturing before/after.
Edit tracker metadata on one foreign-qualification row: the obligation assessment (confirm the obligation — unlocks prepare — or dismiss as not_required with a REQUIRED assessment note; both are human-only CPA judgments that reject agent principals, design D5), assignee and notes. Writes a structured audit event capturing before/after.
foreign_qualifications.writeRequest body ForeignQualificationUpdateForeignQualificationMetadataInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
foreignQualificationId | string | required | — |
obligationAssessment | enum: "confirmed" | "not_required" | optional | — |
assessmentNote | string | optional | — |
assignee | string | optional | — |
notes | string | optional | — |
reason | string | required | — |
Responses
- 200Successful invocation
ForeignQualificationUpdateForeignQualificationMetadataOutputField Type Required Description qualificationobjectrequired — changedFieldsarray<string>required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `foreign_qualifications.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/foreign-qualification.prepareForeignQualificationPopulate a foreign qualification's prepared form payload (officers, principal address, home-state formation details, the customer's OWN foreign-state registered agent — Kreto is not the RA — and the good-standing checklist) and transition pending/manual_required → prepared. APPROVAL_REQUIRED and UI-only. HARD GUARDS (design D5/D7): refused unless obligation_assessment='confirmed' (a human CPA judgment) and the catalog cell is at/above the display gate; a required good-standing certificate must be recorded as obtained. PII in form_payload is scrubbed from audit rows.
Populate a foreign qualification's prepared form payload (officers, principal address, home-state formation details, the customer's OWN foreign-state registered agent — Kreto is not the RA — and the good-standing checklist) and transition pending/manual_required → prepared. APPROVAL_REQUIRED and UI-only. HARD GUARDS (design D5/D7): refused unless obligation_assessment='confirmed' (a human CPA judgment) and the catalog cell is at/above the display gate; a required good-standing certificate must be recorded as obtained. PII in form_payload is scrubbed from audit rows.
foreign_qualifications.writeRequest body ForeignQualificationPrepareForeignQualificationInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
foreignQualificationId | string | required | — |
formPayload | object | required | — |
Responses
- 200Successful invocation
ForeignQualificationPrepareForeignQualificationOutputField Type Required Description qualificationobjectrequired — fromStatusenum: "pending" | "prepared" | "submitted" | "approved" | …required — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `foreign_qualifications.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/foreign-qualification.requestForeignQualConciergeFilingRecord the '$149 File it for me' concierge request on a prepared foreign qualification: stamps filing_requested_at/by (design D3 — an annotation on 'prepared', not a status) and charges the $149 as a standalone Stripe invoice (created + finalized, so it bills every customer; owner 2026-07-10; fail-closed when STRIPE_SECRET_KEY is unset; double-bill safe). APPROVAL_REQUIRED: this moves money, so the v1 dispatcher blocks non-interactive API-key / MCP callers (KRT-1657) — it requires an interactive human session. Concierge gate (design D7): the catalog cell must be 'verified'. The click by an authenticated CPA/owner IS the recorded filing authorization. The state's own filing fee is separate and paid to the state.
Record the '$149 File it for me' concierge request on a prepared foreign qualification: stamps filing_requested_at/by (design D3 — an annotation on 'prepared', not a status) and charges the $149 as a standalone Stripe invoice (created + finalized, so it bills every customer; owner 2026-07-10; fail-closed when STRIPE_SECRET_KEY is unset; double-bill safe). APPROVAL_REQUIRED: this moves money, so the v1 dispatcher blocks non-interactive API-key / MCP callers (KRT-1657) — it requires an interactive human session. Concierge gate (design D7): the catalog cell must be 'verified'. The click by an authenticated CPA/owner IS the recorded filing authorization. The state's own filing fee is separate and paid to the state.
foreign_qualifications.writeRequest body ForeignQualificationRequestForeignQualConciergeFilingInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
foreignQualificationId | string | required | — |
Responses
- 200Successful invocation
ForeignQualificationRequestForeignQualConciergeFilingOutputField Type Required Description qualificationobjectrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `foreign_qualifications.write`.
L2ErrorEnvelope - 409Not available (code `STATE_FILING_NOT_AVAILABLE`): Kreto does not file foreign qualifications yet. Nothing is written, nothing is charged, nothing is enqueued and the row's status does not change.
L2StateFilingNotAvailableRefusal - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/foreign-qualification.transitionForeignQualificationStatusApply a matrix-gated status transition to a foreign-qualification tracker row (exact ear/esr 7-status machine). Supports the mark-filed extras (confirmationNumber + filedAt on → submitted), the approval record (qualificationDate + sosFileNumber on → approved — this becomes the authoritative annual-reports foreign anchor, design D9), the confirmedImmediately chain, and the manual withdrawal record (withdrawnAt on → expired; the sweep never writes expired). Every hop writes a hash-chained audit_events row.
Apply a matrix-gated status transition to a foreign-qualification tracker row (exact ear/esr 7-status machine). Supports the mark-filed extras (confirmationNumber + filedAt on → submitted), the approval record (qualificationDate + sosFileNumber on → approved — this becomes the authoritative annual-reports foreign anchor, design D9), the confirmedImmediately chain, and the manual withdrawal record (withdrawnAt on → expired; the sweep never writes expired). Every hop writes a hash-chained audit_events row.
foreign_qualifications.writeRequest body ForeignQualificationTransitionForeignQualificationStatusInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
foreignQualificationId | string | required | — |
newStatus | enum: "pending" | "prepared" | "submitted" | "approved" | … | required | — |
reason | string | required | — |
confirmationNumber | string | optional | — |
filedAt | string · date-time | optional | — |
qualificationDate | string | optional | — |
sosFileNumber | string | optional | — |
confirmedImmediately | boolean | optional | — |
acknowledgedNoQualificationDate | boolean | optional | — |
withdrawnAt | string | optional | — |
lastError | string | optional | — |
Responses
- 200Successful invocation
ForeignQualificationTransitionForeignQualificationStatusOutputField Type Required Description qualificationobjectrequired — fromStatusenum: "pending" | "prepared" | "submitted" | "approved" | …required — toStatusenum: "pending" | "prepared" | "submitted" | "approved" | …required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `foreign_qualifications.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:handbooks13
POST/v1/handbooks.listHandbookModulesList the handbook policy-module catalog (global config): per (jurisdiction × topic) modules with each module's CURRENT version and its per-version verification-ladder status, plus honest per-status counts. Assembly gate is verified-only (§0-1b): anything below 'verified' must never be rendered on a tenant surface as usable text.
List the handbook policy-module catalog (global config): per (jurisdiction × topic) modules with each module's CURRENT version and its per-version verification-ladder status, plus honest per-status counts. Assembly gate is verified-only (§0-1b): anything below 'verified' must never be rendered on a tenant surface as usable text.
handbooks.readRequest body HandbooksListHandbookModulesInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
jurisdiction | string | optional | — |
category | enum: "federal_core" | "state_required" | "state_recommended" | optional | — |
verificationStatus | enum: "unverified" | "human_reviewed" | "verified" | optional | — |
Responses
- 200Successful invocation
HandbooksListHandbookModulesOutputField Type Required Description modulesarray<object>required — countsobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `handbooks.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/handbooks.getHandbookModuleFetch one handbook policy module by (jurisdiction, topic) with its FULL version history (each version carries its own ladder status — verifying v3 says nothing about v4). Null module = no catalog coverage; the caller renders a coverage gap, never a blank.
Fetch one handbook policy module by (jurisdiction, topic) with its FULL version history (each version carries its own ladder status — verifying v3 says nothing about v4). Null module = no catalog coverage; the caller renders a coverage gap, never a blank.
handbooks.readRequest body HandbooksGetHandbookModuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
jurisdiction | string | required | — |
topic | string | required | — |
Responses
- 200Successful invocation
HandbooksGetHandbookModuleOutputField Type Required Description moduleobjectrequired — versionsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `handbooks.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/handbooks.proposeHandbookModuleResearch-agent write path for the policy-module catalog. Creates the (jurisdiction, topic) module identity if missing and appends a NEW version row at verification_status='unverified' with mandatory provenance. NEVER touches an existing verified version (AC-22 clone) — a law change is a new version, promoted only by a human (AC-20 wall). Inputs are public (jurisdiction, topic) facts — PII-free by construction.
Research-agent write path for the policy-module catalog. Creates the (jurisdiction, topic) module identity if missing and appends a NEW version row at verification_status='unverified' with mandatory provenance. NEVER touches an existing verified version (AC-22 clone) — a law change is a new version, promoted only by a human (AC-20 wall). Inputs are public (jurisdiction, topic) facts — PII-free by construction.
handbooks.writeRequest body HandbooksProposeHandbookModuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
jurisdiction | string | required | — |
topic | string | required | — |
title | string | required | — |
category | enum: "federal_core" | "state_required" | "state_recommended" | required | — |
minEmployeeCount | integer | optional | — |
applicability | object | optional | — |
displayOrder | integer | optional | — |
bodyMd | string | required | — |
changeSummary | string | optional | — |
lawEffectiveDate | string | optional | — |
statuteCitations | array<object> | required | — |
draftedBy | enum: "research_agent" | "human" | optional | — |
draftProvenance | object | optional | — |
sourceUrl | string · uri | required | — |
retrievedAt | string · date-time | required | — |
notes | string | optional | — |
Responses
- 200Successful invocation
HandbooksProposeHandbookModuleOutputField Type Required Description moduleobjectrequired — versionobjectrequired — moduleCreatedbooleanrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `handbooks.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/handbooks.proposeStateRequiredTopicResearch-agent write path for the §3A2 completeness registry — which topics a state REQUIRES at what headcount (a fee-catalog-class checkable fact). Upserts one (jurisdiction, topic) row as 'unverified' with mandatory provenance. REFUSES to touch a row already at human_reviewed/verified (no-downgrade AC-22 clone). The §4 gap computation only trusts VERIFIED registry rows.
Research-agent write path for the §3A2 completeness registry — which topics a state REQUIRES at what headcount (a fee-catalog-class checkable fact). Upserts one (jurisdiction, topic) row as 'unverified' with mandatory provenance. REFUSES to touch a row already at human_reviewed/verified (no-downgrade AC-22 clone). The §4 gap computation only trusts VERIFIED registry rows.
handbooks.writeRequest body HandbooksProposeStateRequiredTopicInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
jurisdiction | string | required | — |
topic | string | required | — |
requirementKind | enum: "required" | "recommended" | required | — |
minEmployeeCount | integer | optional | — |
applicability | object | optional | — |
statuteCitations | array<object> | required | — |
sourceUrl | string · uri | required | — |
retrievedAt | string · date-time | required | — |
notes | string | optional | — |
Responses
- 200Successful invocation
HandbooksProposeStateRequiredTopicOutputField Type Required Description topicRowobjectrequired — createdbooleanrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `handbooks.write`.
L2ErrorEnvelope - 409Reviewed cell locked (code `REVIEWED_CELL_LOCKED`): the registry row is already human-reviewed or verified — an agent proposal may not overwrite it. Nothing is written.
L2HandbookReviewedCellLockedRefusal - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/handbooks.promoteHandbookModuleVersionPromote one policy-module VERSION along the verification ladder (unverified → human_reviewed → verified). APPROVAL_REQUIRED and UI-only: rejects any non-human principal (AC-20 wall), refuses missing source_url provenance. The promotion UI shows the §0-1(b) honest definition of what the click asserts. On 'verified': the prior verified version gets superseded_at and current_version_id advances (§7 step 3). 'human_reviewed' remains admin-queue-only — it renders on NO tenant surface (§0-1b).
Promote one policy-module VERSION along the verification ladder (unverified → human_reviewed → verified). APPROVAL_REQUIRED and UI-only: rejects any non-human principal (AC-20 wall), refuses missing source_url provenance. The promotion UI shows the §0-1(b) honest definition of what the click asserts. On 'verified': the prior verified version gets superseded_at and current_version_id advances (§7 step 3). 'human_reviewed' remains admin-queue-only — it renders on NO tenant surface (§0-1b).
handbooks.writeRequest body HandbooksPromoteHandbookModuleVersionInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
moduleVersionId | string | required | — |
targetStatus | enum: "human_reviewed" | "verified" | required | — |
reason | string | required | — |
Responses
- 200Successful invocation
HandbooksPromoteHandbookModuleVersionOutputField Type Required Description versionobjectrequired — fromStatusenum: "unverified" | "human_reviewed" | "verified"required — toStatusenum: "unverified" | "human_reviewed" | "verified"required — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden. `SYSTEM_ADMIN_REQUIRED`: the caller is not a Kreto system administrator on an interactive session — the global catalog every firm pins (handbook modules, state filing / foreign-qualification / annual-report / e-delivery rules) is staff-only, exactly as on the REST admin door. Checked before any catalog read, so the answer is the same whether or not the row exists. `STEP_UP_REQUIRED`: the dispatcher's APPROVAL_REQUIRED gate (an API key, or no fresh re-authentication). Nothing is written.
one of (L2SystemAdminRequiredRefusal · L2ErrorEnvelope) - 404Not found (code `NOT_FOUND`): the row the input names is not in the caller's tenant (or the catalog), or handbooks are disabled for the tenant. Nothing is written.
L2HandbookNotFoundRefusal - 409`PROVENANCE_REQUIRED`: the row has no `source_url` — provenance is mandatory before promotion. `ILLEGAL_TRANSITION`: the version is emergency-retired, or the move is not up the verification ladder (the ladder is monotonic; no downgrade). Nothing is written.
one of (L2HandbookProvenanceRequiredRefusal · L2HandbookIllegalTransitionRefusal) - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/handbooks.promoteStateRequiredTopicPromote a §3A2 completeness-registry row along the verification ladder. APPROVAL_REQUIRED and UI-only (AC-20 wall — rejects non-human principals; refuses missing source_url). Only VERIFIED registry rows feed the honest-gaps computation.
Promote a §3A2 completeness-registry row along the verification ladder. APPROVAL_REQUIRED and UI-only (AC-20 wall — rejects non-human principals; refuses missing source_url). Only VERIFIED registry rows feed the honest-gaps computation.
handbooks.writeRequest body HandbooksPromoteStateRequiredTopicInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
jurisdiction | string | required | — |
topic | string | required | — |
targetStatus | enum: "human_reviewed" | "verified" | required | — |
reason | string | required | — |
Responses
- 200Successful invocation
HandbooksPromoteStateRequiredTopicOutputField Type Required Description topicRowobjectrequired — fromStatusenum: "unverified" | "human_reviewed" | "verified"required — toStatusenum: "unverified" | "human_reviewed" | "verified"required — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden. `SYSTEM_ADMIN_REQUIRED`: the caller is not a Kreto system administrator on an interactive session — the global catalog every firm pins (handbook modules, state filing / foreign-qualification / annual-report / e-delivery rules) is staff-only, exactly as on the REST admin door. Checked before any catalog read, so the answer is the same whether or not the row exists. `STEP_UP_REQUIRED`: the dispatcher's APPROVAL_REQUIRED gate (an API key, or no fresh re-authentication). Nothing is written.
one of (L2SystemAdminRequiredRefusal · L2ErrorEnvelope) - 404Not found (code `NOT_FOUND`): the row the input names is not in the caller's tenant (or the catalog), or handbooks are disabled for the tenant. Nothing is written.
L2HandbookNotFoundRefusal - 409`PROVENANCE_REQUIRED`: the row has no `source_url` — provenance is mandatory before promotion. `ILLEGAL_TRANSITION`: the move is not up the verification ladder (the ladder is monotonic; no downgrade). Nothing is written.
one of (L2HandbookProvenanceRequiredRefusal · L2HandbookIllegalTransitionRefusal) - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/handbooks.retireHandbookModuleVersionEMERGENCY version-level retire (§0-5d): a repealed/amended/enjoined statute pulls a verified version from assembly IMMEDIATELY, without waiting for a replacement to clear review — the ONE sanctioned exemption from no-downgrade. APPROVAL_REQUIRED, human-only, reason mandatory. Existing pins are unaffected (reconstructability holds) but published handbooks pinning this version get flagged by checkHandbookDrift.
EMERGENCY version-level retire (§0-5d): a repealed/amended/enjoined statute pulls a verified version from assembly IMMEDIATELY, without waiting for a replacement to clear review — the ONE sanctioned exemption from no-downgrade. APPROVAL_REQUIRED, human-only, reason mandatory. Existing pins are unaffected (reconstructability holds) but published handbooks pinning this version get flagged by checkHandbookDrift.
handbooks.writeRequest body HandbooksRetireHandbookModuleVersionInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
moduleVersionId | string | required | — |
reason | string | required | — |
Responses
- 200Successful invocation
HandbooksRetireHandbookModuleVersionOutputField Type Required Description versionobjectrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden. `SYSTEM_ADMIN_REQUIRED`: the caller is not a Kreto system administrator on an interactive session — the global catalog every firm pins (handbook modules, state filing / foreign-qualification / annual-report / e-delivery rules) is staff-only, exactly as on the REST admin door. Checked before any catalog read, so the answer is the same whether or not the row exists. `STEP_UP_REQUIRED`: the dispatcher's APPROVAL_REQUIRED gate (an API key, or no fresh re-authentication). Nothing is written.
one of (L2SystemAdminRequiredRefusal · L2ErrorEnvelope) - 404Not found (code `NOT_FOUND`): the row the input names is not in the caller's tenant (or the catalog), or handbooks are disabled for the tenant. Nothing is written.
L2HandbookNotFoundRefusal - 409Illegal transition (code `ILLEGAL_TRANSITION`): the version is already retired. Nothing is written.
L2HandbookIllegalTransitionRefusal - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/handbooks.listHandbooksList the tenant's handbook instances (per entity, versioned), filterable by entity and status. Paginated (default 25). Tenant-scoped via the invocation context. Each draft carries `publish_refusal`: the `missing` set publishHandbook would refuse it with (no policy sections), or null when it may publish.
List the tenant's handbook instances (per entity, versioned), filterable by entity and status. Paginated (default 25). Tenant-scoped via the invocation context. Each draft carries `publish_refusal`: the `missing` set publishHandbook would refuse it with (no policy sections), or null when it may publish.
handbooks.readRequest body HandbooksListHandbooksInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | optional | — |
status | enum: "draft" | "published" | "superseded" | "archived" | optional | — |
limit | integer | optional | — |
offset | integer | optional | — |
Responses
- 200Successful invocation
HandbooksListHandbooksOutputField Type Required Description handbooksarray<object>required — totalnumberrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `handbooks.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/handbooks.generateHandbookDraftGenerate a handbook DRAFT for an entity: deterministic module selection (selectHandbookModules) from live operating states + per-state headcount, assembly gate = verified-only, honest coverage gaps computed against the §3A2 registry (uncovered states gap UNCONDITIONALLY), generation-time staleness warnings (§0-5c). Pins exact module-version ids. Publishing is a separate APPROVAL_REQUIRED step with the blocking disclaimer checkbox.
Generate a handbook DRAFT for an entity: deterministic module selection (selectHandbookModules) from live operating states + per-state headcount, assembly gate = verified-only, honest coverage gaps computed against the §3A2 registry (uncovered states gap UNCONDITIONALLY), generation-time staleness warnings (§0-5c). Pins exact module-version ids. Publishing is a separate APPROVAL_REQUIRED step with the blocking disclaimer checkbox.
handbooks.writeRequest body HandbooksGenerateHandbookDraftInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
title | string | optional | — |
companyInputs | object | optional | — |
Responses
- 200Successful invocation
HandbooksGenerateHandbookDraftOutputField Type Required Description handbookobjectrequired — selectionobjectrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `handbooks.write`.
L2ErrorEnvelope - 404Not found (code `NOT_FOUND`): the row the input names is not in the caller's tenant (or the catalog), or handbooks are disabled for the tenant. Nothing is written.
L2HandbookNotFoundRefusal - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/handbooks.publishHandbookPublish a draft handbook. APPROVAL_REQUIRED and UI-only: the §0-2(b) blocking disclaimer checkbox is part of the contract — refuses disclaimerAccepted !== true, stamps disclaimer_ack_at/by, records the exact checkbox copy in the hash-chained audit trail, freezes the pins + states_snapshot, supersedes the prior published version, and queues the PDF render (onboarding-docs queue posture — never a half-written published row).
Publish a draft handbook. APPROVAL_REQUIRED and UI-only: the §0-2(b) blocking disclaimer checkbox is part of the contract — refuses disclaimerAccepted !== true, stamps disclaimer_ack_at/by, records the exact checkbox copy in the hash-chained audit trail, freezes the pins + states_snapshot, supersedes the prior published version, and queues the PDF render (onboarding-docs queue posture — never a half-written published row).
handbooks.writeRequest body HandbooksPublishHandbookInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
handbookId | string | required | — |
disclaimerAccepted | boolean | required | — |
Responses
- 200Successful invocation
HandbooksPublishHandbookOutputField Type Required Description handbookobjectrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `handbooks.write`.
L2ErrorEnvelope - 404Not found (code `NOT_FOUND`): the row the input names is not in the caller's tenant (or the catalog), or handbooks are disabled for the tenant. Nothing is written.
L2HandbookNotFoundRefusal - 409The draft cannot be published — the same two refusals the REST door (`POST /api/handbooks/{id}/publish`) answers, in the L2 envelope. `HANDBOOK_HAS_NO_SECTIONS`: it has no policy sections; `missing` names the jurisdictions and required topics the verified catalog lacks. `ILLEGAL_TRANSITION`: the handbook is not a draft. Nothing is written.
L2HandbookPublishRefusal - 422Disclaimer required (code `DISCLAIMER_REQUIRED`): `disclaimerAccepted` is not `true` — the blocking attorney-review checkbox was not ticked. Nothing is written.
L2HandbookDisclaimerRequiredRefusal - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/handbooks.sendHandbookAcknowledgmentsCreate one acknowledgment EVIDENCE row per active roster employee of the published handbook's entity — snapshotting name/email/work_state at send (§3E) — and email each a tokenized link (signature_requests clone). Employees without email land in an honest 'cannot reach' list. Idempotent: existing (handbook, employee) rows are skipped.
Create one acknowledgment EVIDENCE row per active roster employee of the published handbook's entity — snapshotting name/email/work_state at send (§3E) — and email each a tokenized link (signature_requests clone). Employees without email land in an honest 'cannot reach' list. Idempotent: existing (handbook, employee) rows are skipped.
handbooks.writeRequest body HandbooksSendHandbookAcknowledgmentsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
handbookId | string | required | — |
Responses
- 200Successful invocation
HandbooksSendHandbookAcknowledgmentsOutputField Type Required Description creatednumberrequired — skippedExistingnumberrequired — cannotReacharray<object>required — emailFailuresnumberrequired — sendOutcomesobjectrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `handbooks.write`.
L2ErrorEnvelope - 404Not found (code `NOT_FOUND`): the row the input names is not in the caller's tenant (or the catalog), or handbooks are disabled for the tenant. Nothing is written.
L2HandbookNotFoundRefusal - 409Illegal transition (code `ILLEGAL_TRANSITION`): the handbook is not published — only published handbooks are sent for acknowledgment. Nothing is written.
L2HandbookIllegalTransitionRefusal - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/handbooks.getHandbookAckStatusThe acknowledgment tracking matrix for one handbook: per-employee sent/viewed/acknowledged status with honest counts. secure_token is never returned. Tenant-scoped.
The acknowledgment tracking matrix for one handbook: per-employee sent/viewed/acknowledged status with honest counts. secure_token is never returned. Tenant-scoped.
handbooks.readRequest body HandbooksGetHandbookAckStatusInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
handbookId | string | required | — |
Responses
- 200Successful invocation
HandbooksGetHandbookAckStatusOutputField Type Required Description acksarray<object>required — countsobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `handbooks.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/handbooks.checkHandbookDriftFlag every published handbook of an entity that is stale on either axis (§7 step 4): pinned-module drift (pins a superseded or emergency-retired version) or selection-input drift (a deterministic re-run of selectHandbookModules against LIVE operating states + headcount diverges from states_snapshot — new state registered, threshold crossed). Read-only; never auto-republishes (§0-5e).
Flag every published handbook of an entity that is stale on either axis (§7 step 4): pinned-module drift (pins a superseded or emergency-retired version) or selection-input drift (a deterministic re-run of selectHandbookModules against LIVE operating states + headcount diverges from states_snapshot — new state registered, threshold crossed). Read-only; never auto-republishes (§0-5e).
handbooks.readRequest body HandbooksCheckHandbookDriftInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
Responses
- 200Successful invocation
HandbooksCheckHandbookDriftOutputField Type Required Description entityIdstringrequired — flagsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `handbooks.read`.
L2ErrorEnvelope - 404Not found (code `NOT_FOUND`): the row the input names is not in the caller's tenant (or the catalog), or handbooks are disabled for the tenant. Nothing is written.
L2HandbookNotFoundRefusal - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:i9-records6
POST/v1/i9-records.createI9RecordRecord Section 1 of an I-9 TRANSCRIBED from a form the employee already completed and signed (section1Data.transcribed_from_signed_original must be true — staff never attest Section 1 themselves). SSN plaintext is encrypted via the public.app_create_secret vault wrapper; only the returned secret UUID is stored on the row. Dual-writes employees.i9_section1_complete = true. APPROVAL_REQUIRED.
Record Section 1 of an I-9 TRANSCRIBED from a form the employee already completed and signed (section1Data.transcribed_from_signed_original must be true — staff never attest Section 1 themselves). SSN plaintext is encrypted via the public.app_create_secret vault wrapper; only the returned secret UUID is stored on the row. Dual-writes employees.i9_section1_complete = true. APPROVAL_REQUIRED.
payroll.writeRequest body I9RecordsCreateI9RecordInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
employeeId | string | required | — |
citizenshipStatus | enum: "us_citizen" | "noncitizen_national" | "lpr" | "authorized_alien" | required | — |
section1Data | object | required | — |
ssn | string | optional | — |
Responses
- 200Successful invocation
I9RecordsCreateI9RecordOutputField Type Required Description idstringrequired — tenant_idstringrequired — employee_idstringrequired — citizenship_statusenum: "us_citizen" | "noncitizen_national" | "lpr" | "authorized_alien" | …required — section1_completed_atstringrequired — section2_completed_atstringrequired — has_ssnbooleanrequired — ssn_last_4stringrequired — retention_untilstringrequired — retention_provisionalbooleanrequired — is_archivedbooleanrequired — has_pdfbooleanrequired — supporting_docs_countintegerrequired — created_atstringrequired — updated_atstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/i9-records.getI9RecordFetch a single I-9 record by id, including decrypted SSN plaintext for the CPA detail view. APPROVAL_REQUIRED. The `ssn` field is redacted from audit outputs while the live caller receives the actual value (R13 mitigation, mirrors credential-vault.getCredential).
Fetch a single I-9 record by id, including decrypted SSN plaintext for the CPA detail view. APPROVAL_REQUIRED. The `ssn` field is redacted from audit outputs while the live caller receives the actual value (R13 mitigation, mirrors credential-vault.getCredential).
payroll.writeRequest body I9RecordsGetI9RecordInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
i9RecordId | string | required | — |
Responses
- 200Successful invocation
I9RecordsGetI9RecordOutputField Type Required Description idstringrequired — tenant_idstringrequired — employee_idstringrequired — citizenship_statusenum: "us_citizen" | "noncitizen_national" | "lpr" | "authorized_alien" | …required — section1_completed_atstringrequired — section2_completed_atstringrequired — has_ssnbooleanrequired — ssn_last_4stringrequired — retention_untilstringrequired — retention_provisionalbooleanrequired — is_archivedbooleanrequired — has_pdfbooleanrequired — supporting_docs_countintegerrequired — created_atstringrequired — updated_atstringrequired — ssnstringrequired — section1_dataobjectrequired — section2_dataobjectrequired — list_a_docsarray<object>required — list_b_docsarray<object>required — list_c_docsarray<object>required — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/i9-records.updateI9RecordSection2Update an i9_record with Section 2 employer-review data. Validates Section 1 completion + USCIS document combination (List A XOR List B+C). Dual-writes employees.i9_section2_complete = true. APPROVAL_REQUIRED.
Update an i9_record with Section 2 employer-review data. Validates Section 1 completion + USCIS document combination (List A XOR List B+C). Dual-writes employees.i9_section2_complete = true. APPROVAL_REQUIRED.
payroll.writeRequest body I9RecordsUpdateI9RecordSection2Inputrequired
| Field | Type | Required | Description |
|---|---|---|---|
i9RecordId | string | required | — |
section2Data | object | required | — |
reviewerUserId | string | optional | — |
Responses
- 200Successful invocation
I9RecordsUpdateI9RecordSection2OutputField Type Required Description idstringrequired — tenant_idstringrequired — employee_idstringrequired — citizenship_statusenum: "us_citizen" | "noncitizen_national" | "lpr" | "authorized_alien" | …required — section1_completed_atstringrequired — section2_completed_atstringrequired — has_ssnbooleanrequired — ssn_last_4stringrequired — retention_untilstringrequired — retention_provisionalbooleanrequired — is_archivedbooleanrequired — has_pdfbooleanrequired — supporting_docs_countintegerrequired — created_atstringrequired — updated_atstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/i9-records.listI9RecordsList I-9 records in the caller's tenant. Returns metadata only (no plaintext SSN, no full section data). The `ssn_last_4` field is left null on this path — fetch via getI9Record for last-4 display. AUTONOMOUS.
List I-9 records in the caller's tenant. Returns metadata only (no plaintext SSN, no full section data). The `ssn_last_4` field is left null on this path — fetch via getI9Record for last-4 display. AUTONOMOUS.
payroll.readRequest body I9RecordsListI9RecordsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
employeeId | string | optional | — |
includeArchived | boolean | optional | — |
Responses
- 200Successful invocation
I9RecordsListI9RecordsOutputField Type Required Description recordsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/i9-records.addI9SupportingDocumentUpload a supporting document (passport image, driver's license, SSN card, etc.) for an I-9 record. Append-only: every upload is stored at its own key in the service-role-only i9-documents bucket (<tenant-id>/<record-id>/list_<a|b|c>_doc_<n>-<version>.<ext>) with its own i9_record_documents row; an earlier upload in the same slot is never removed or overwritten. i9_records.supporting_docs_paths lists the newest path per slot. APPROVAL_REQUIRED.
Upload a supporting document (passport image, driver's license, SSN card, etc.) for an I-9 record. Append-only: every upload is stored at its own key in the service-role-only i9-documents bucket (<tenant-id>/<record-id>/list_<a|b|c>_doc_<n>-<version>.<ext>) with its own i9_record_documents row; an earlier upload in the same slot is never removed or overwritten. i9_records.supporting_docs_paths lists the newest path per slot. APPROVAL_REQUIRED.
payroll.writeRequest body I9RecordsAddI9SupportingDocumentInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
i9RecordId | string | required | — |
list | enum: "a" | "b" | "c" | required | — |
index | integer | required | — |
filename | string | required | — |
mimeType | string | required | — |
fileBytes | string · binary | required | — |
Responses
- 200Successful invocation
I9RecordsAddI9SupportingDocumentOutputField Type Required Description idstringrequired — tenant_idstringrequired — employee_idstringrequired — citizenship_statusenum: "us_citizen" | "noncitizen_national" | "lpr" | "authorized_alien" | …required — section1_completed_atstringrequired — section2_completed_atstringrequired — has_ssnbooleanrequired — ssn_last_4stringrequired — retention_untilstringrequired — retention_provisionalbooleanrequired — is_archivedbooleanrequired — has_pdfbooleanrequired — supporting_docs_countintegerrequired — created_atstringrequired — updated_atstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/i9-records.generateI9PdfRender a USCIS Form I-9 PDF from i9_records data and store it as a new version at i9-documents/<tenant-id>/<id>/form-<version>.pdf with its own i9_record_documents row (kind generated); i9_records.pdf_path points at the newest. Regenerating adds a version and never overwrites or removes an earlier one. SSN is decrypted internally and rendered into the PDF; never returned to the caller. APPROVAL_REQUIRED.
Render a USCIS Form I-9 PDF from i9_records data and store it as a new version at i9-documents/<tenant-id>/<id>/form-<version>.pdf with its own i9_record_documents row (kind generated); i9_records.pdf_path points at the newest. Regenerating adds a version and never overwrites or removes an earlier one. SSN is decrypted internally and rendered into the PDF; never returned to the caller. APPROVAL_REQUIRED.
payroll.writeRequest body I9RecordsGenerateI9PdfInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
i9RecordId | string | required | — |
Responses
- 200Successful invocation
I9RecordsGenerateI9PdfOutputField Type Required Description i9RecordIdstringrequired — byte_sizeintegerrequired — document_idstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:location-detection4
POST/v1/location-detection.recordLocationChangeRecord a detected or operator-entered work-state transition for an employee. Idempotent on (employee_id, effective_date) — re-recording the same date returns the existing row's id with isDuplicate=true. NOTIFY — operator decision OR payroll-detection adapter call; the follow-up confirmLocationChange gates SUI/PFL jurisdiction routing.
Record a detected or operator-entered work-state transition for an employee. Idempotent on (employee_id, effective_date) — re-recording the same date returns the existing row's id with isDuplicate=true. NOTIFY — operator decision OR payroll-detection adapter call; the follow-up confirmLocationChange gates SUI/PFL jurisdiction routing.
payroll.writeRequest body LocationDetectionRecordLocationChangeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
employeeId | string | required | — |
toState | string | required | — |
fromState | string | optional | — |
effectiveDate | string | required | — |
source | enum: "payroll_detection" | "operator_manual" | "finch_sync" | "gusto_sync" | … | required | — |
metadata | object | optional | — |
Responses
- 200Successful invocation
LocationDetectionRecordLocationChangeOutputField Type Required Description idstringrequired — employeeIdstringrequired — toStatestringrequired — effectiveDatestringrequired — isDuplicatebooleanrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/location-detection.listLocationChangesReturn tenant-scoped work-state transition history. Optional `entityId` / `employeeId` filters narrow the result; `onlyConfirmed` filters to operator-confirmed rows. Ordered most-recent effective_date first. AUTONOMOUS — read-only.
Return tenant-scoped work-state transition history. Optional `entityId` / `employeeId` filters narrow the result; `onlyConfirmed` filters to operator-confirmed rows. Ordered most-recent effective_date first. AUTONOMOUS — read-only.
payroll.readRequest body LocationDetectionListLocationChangesInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | optional | — |
employeeId | string | optional | — |
onlyConfirmed | boolean | optional | — |
limit | integer | optional | — |
Responses
- 200Successful invocation
LocationDetectionListLocationChangesOutputField Type Required Description changesarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/location-detection.confirmLocationChangeOperator confirms a detected work-state transition is real (not a payroll-data typo). Sets is_confirmed=true + confirmed_at + confirmed_by. Once confirmed, the locationDetection agent treats the row as the canonical state-of-record for the employee from effective_date forward. NOTIFY — operator override.
Operator confirms a detected work-state transition is real (not a payroll-data typo). Sets is_confirmed=true + confirmed_at + confirmed_by. Once confirmed, the locationDetection agent treats the row as the canonical state-of-record for the employee from effective_date forward. NOTIFY — operator override.
payroll.writeRequest body LocationDetectionConfirmLocationChangeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
id | string | required | — |
Responses
- 200Successful invocation
LocationDetectionConfirmLocationChangeOutputField Type Required Description idstringrequired — isConfirmedenum: truerequired — confirmedAtstringrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/location-detection.getLatestLocationByEmployeeReturn the most recent confirmed work-state transition for an employee (as-of optional date; defaults to now). Returns {toState: null} when no confirmed rows exist. AUTONOMOUS — read-only.
Return the most recent confirmed work-state transition for an employee (as-of optional date; defaults to now). Returns {toState: null} when no confirmed rows exist. AUTONOMOUS — read-only.
payroll.readRequest body LocationDetectionGetLatestLocationByEmployeeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
employeeId | string | required | — |
asOf | string | optional | — |
Responses
- 200Successful invocation
LocationDetectionGetLatestLocationByEmployeeOutputField Type Required Description employeeIdstringrequired — toStatestringrequired — effectiveDatestringrequired — isConfirmedbooleanrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:mailbox9
POST/v1/mailbox.listInboundList inbound mail for an employer entity. Returns received mail rows (newest first) with sender info, status, OCR text, and notice linkage. Tenant-scoped; RLS enforced with a defense-in-depth cross-tenant assertion. AUTONOMOUS — read-only, recoverable, no agency impact.
List inbound mail for an employer entity. Returns received mail rows (newest first) with sender info, status, OCR text, and notice linkage. Tenant-scoped; RLS enforced with a defense-in-depth cross-tenant assertion. AUTONOMOUS — read-only, recoverable, no agency impact.
notices.readRequest body MailboxListInboundInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string · uuid | required | — |
Responses
- 200Successful invocation
MailboxListInboundOutputField Type Required Description itemsarray<object>required — totalintegerrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/mailbox.discardHeldDiscard a piece of inbound mail HELD for a removed employer: status held -> failed, never processed into a notice. Firm plane only (refused for any entity-restricted caller). 409 MAIL_NOT_HELD when the piece is not held. The scan stays in storage; the employer plane never sees it. NOTIFY — firm triage write, audited.
Discard a piece of inbound mail HELD for a removed employer: status held -> failed, never processed into a notice. Firm plane only (refused for any entity-restricted caller). 409 MAIL_NOT_HELD when the piece is not held. The scan stays in storage; the employer plane never sees it. NOTIFY — firm triage write, audited.
notices.writeRequest body MailboxDiscardHeldInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string · uuid | required | — |
mailId | string · uuid | required | — |
reason | enum: "employer_removed" | required | — |
Responses
- 200Successful invocation
MailboxDiscardHeldOutputField Type Required Description idstring · uuidrequired — statusenum: "failed"required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/mailbox.laneSummaryCount the CPA mailbox lanes (approve / input / active / done) over every live notice of the firm, through the one lane ladder. Firm users only: an entity-scoped caller gets 403 MAILBOX_CPA_ONLY before any read; a firm whose CPA_MAILBOX flag is off gets 403 FEATURE_DISABLED. `capped` true means the population hit the row cap and every count is a lower bound. `overdueByLane` counts past-deadline notices per lane over the same population. `attribution.unconfirmed` counts notices, not employers. `asOf` is this read's own instant; a listLane total read later may drift from it. AUTONOMOUS — read-only.
Count the CPA mailbox lanes (approve / input / active / done) over every live notice of the firm, through the one lane ladder. Firm users only: an entity-scoped caller gets 403 MAILBOX_CPA_ONLY before any read; a firm whose CPA_MAILBOX flag is off gets 403 FEATURE_DISABLED. `capped` true means the population hit the row cap and every count is a lower bound. `overdueByLane` counts past-deadline notices per lane over the same population. `attribution.unconfirmed` counts notices, not employers. `asOf` is this read's own instant; a listLane total read later may drift from it. AUTONOMOUS — read-only.
notices.readRequest body MailboxLaneSummaryInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string · uuid | optional | Narrow to the notices attached to this employer (entity id). An id that is not one of the firm's employers returns the empty slice. |
owner | enum: "mine" | "unassigned" | optional | 'mine' = notices the caller owns; 'unassigned' = OPEN notices nobody owns (the same set as the work queue's Unassigned count). Omitted = every owner. |
Responses
- 200Successful invocation
MailboxLaneSummaryOutputField Type Required Description lanesobjectrequired — overdueByLaneobjectrequired Per lane, how many notices are past their governing deadline (the Overdue chip's predicate: junk, terminal and recorded-verdict notices are never overdue). Counted over the same firm-wide population as `lanes`, so each value is <= that lane's count. notANoticeInActiveintegerrequired — populationintegerrequired — cappedbooleanrequired — windowobjectrequired — attributionobjectrequired — asOfstringrequired ISO-8601 instant of this read (one clock call per read). laneSummary and listLane are separate reads, each with its own asOf: a listLane total may drift from the summary's lane count when the firm changed in between — re-read laneSummary to reconcile. - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/mailbox.listLaneList one CPA mailbox lane, paged after classification (deadline asc nulls last, then id). Rows are the /api/notices CPA row shape. `total` is the lane's count and equals laneSummary's for the same state — the two are separate reads with their own `asOf`, so a page total may drift from the summary tab until laneSummary is re-read. Firm users only: an entity-scoped caller gets 403 MAILBOX_CPA_ONLY before any read; a firm whose CPA_MAILBOX flag is off gets 403 FEATURE_DISABLED. AUTONOMOUS — read-only.
List one CPA mailbox lane, paged after classification (deadline asc nulls last, then id). Rows are the /api/notices CPA row shape. `total` is the lane's count and equals laneSummary's for the same state — the two are separate reads with their own `asOf`, so a page total may drift from the summary tab until laneSummary is re-read. Firm users only: an entity-scoped caller gets 403 MAILBOX_CPA_ONLY before any read; a firm whose CPA_MAILBOX flag is off gets 403 FEATURE_DISABLED. AUTONOMOUS — read-only.
notices.readRequest body MailboxListLaneInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
lane | enum: "approve" | "input" | "active" | "done" | required | — |
offset | integer | optional | — |
limit | integer | optional | — |
entityId | string · uuid | optional | Narrow to the notices attached to this employer (entity id). An id that is not one of the firm's employers returns the empty slice. |
owner | enum: "mine" | "unassigned" | optional | 'mine' = notices the caller owns; 'unassigned' = OPEN notices nobody owns (the same set as the work queue's Unassigned count). Omitted = every owner. |
Responses
- 200Successful invocation
MailboxListLaneOutputField Type Required Description rowsarray<object>required — totalintegerrequired — cappedbooleanrequired — asOfstringrequired ISO-8601 instant of this read (one clock call per read). laneSummary and listLane are separate reads, each with its own asOf: a listLane total may drift from the summary's lane count when the firm changed in between — re-read laneSummary to reconcile. - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/mailbox.listOtherMailList the firm's OTHER MAIL: scanned letters and forwarded email that did not become a notice (held for a removed employer, not a notice, could not be read, no attachment, looked like junk), newest first, one page of 50 per source. Sender shown as a domain or PII-floored name; no body. emailChecked=false means forwarded email could not be read. Firm plane only (403 OTHER_MAIL_FIRM_ONLY otherwise). AUTONOMOUS — read-only.
List the firm's OTHER MAIL: scanned letters and forwarded email that did not become a notice (held for a removed employer, not a notice, could not be read, no attachment, looked like junk), newest first, one page of 50 per source. Sender shown as a domain or PII-floored name; no body. emailChecked=false means forwarded email could not be read. Firm plane only (403 OTHER_MAIL_FIRM_ONLY otherwise). AUTONOMOUS — read-only.
notices.readRequest body MailboxListOtherMailInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
page | integer | optional | — |
Responses
- 200Successful invocation
MailboxListOtherMailOutputField Type Required Description itemsarray<object>required — letterTotalintegerrequired — emailTotalintegerrequired — emailCheckedbooleanrequired — heldTotalintegerrequired — unknownAddressEmailTotalintegerrequired — unknownAddressResendToobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/mailbox.releaseHeldRelease a scanned letter HELD for a removed employer: held -> received and a mailbox_ingest job, so it becomes a notice through the normal funnel. The employer must be restored first (409 EMPLOYER_STILL_REMOVED). 409 MAIL_NOT_OTHER_MAIL when the piece is not held (one body for unknown, foreign and wrong-state ids). Firm plane only. NOTIFY — firm triage write, audited.
Release a scanned letter HELD for a removed employer: held -> received and a mailbox_ingest job, so it becomes a notice through the normal funnel. The employer must be restored first (409 EMPLOYER_STILL_REMOVED). 409 MAIL_NOT_OTHER_MAIL when the piece is not held (one body for unknown, foreign and wrong-state ids). Firm plane only. NOTIFY — firm triage write, audited.
notices.writeRequest body MailboxReleaseHeldInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
mailId | string · uuid | required | — |
Responses
- 200Successful invocation
MailboxReleaseHeldOutputField Type Required Description idstring · uuidrequired — statusenum: "received"required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/mailbox.markAsNotice'This is a notice' for a scanned letter that did not become one (not a notice, or could not be read): -> received and a mailbox_ingest job, so it becomes a notice through the normal funnel. Never writes notices directly. 409 MAIL_NOT_OTHER_MAIL when the piece is not in such a state (one body for unknown, foreign and wrong-state ids). Firm plane only. NOTIFY — firm triage write, audited.
'This is a notice' for a scanned letter that did not become one (not a notice, or could not be read): -> received and a mailbox_ingest job, so it becomes a notice through the normal funnel. Never writes notices directly. 409 MAIL_NOT_OTHER_MAIL when the piece is not in such a state (one body for unknown, foreign and wrong-state ids). Firm plane only. NOTIFY — firm triage write, audited.
notices.writeRequest body MailboxMarkAsNoticeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
mailId | string · uuid | required | — |
Responses
- 200Successful invocation
MailboxMarkAsNoticeOutputField Type Required Description idstring · uuidrequired — statusenum: "received"required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/mailbox.dismissOtherMail'Dismiss' for mail that did not become a notice. source='letter' (default): a scanned letter (not a notice, or could not be read); stamps dismissed_at, which starts the 7-day clock on the original scan; 'This is a notice' (mailbox.markAsNotice) undoes it. source='email': a forwarded email of this firm with no notice (no attachment, attachment not read, looked like junk, could not be read); stamps dismissed_at, which starts the 7-day clock on the email text. The status is unchanged. 409 MAIL_NOT_OTHER_MAIL when the row is not such mail or is already dismissed (one body for unknown, foreign, unknown-address and wrong-state ids). clear=true with source='email' is 'Keep this email': clears dismissed_at on a dismissed email (409 when it is not dismissed; clear on a letter is 409, use markAsNotice). Firm plane only. NOTIFY — firm triage write, audited.
'Dismiss' for mail that did not become a notice. source='letter' (default): a scanned letter (not a notice, or could not be read); stamps dismissed_at, which starts the 7-day clock on the original scan; 'This is a notice' (mailbox.markAsNotice) undoes it. source='email': a forwarded email of this firm with no notice (no attachment, attachment not read, looked like junk, could not be read); stamps dismissed_at, which starts the 7-day clock on the email text. The status is unchanged. 409 MAIL_NOT_OTHER_MAIL when the row is not such mail or is already dismissed (one body for unknown, foreign, unknown-address and wrong-state ids). clear=true with source='email' is 'Keep this email': clears dismissed_at on a dismissed email (409 when it is not dismissed; clear on a letter is 409, use markAsNotice). Firm plane only. NOTIFY — firm triage write, audited.
notices.writeRequest body MailboxDismissOtherMailInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
mailId | string · uuid | required | — |
source | enum: "letter" | "email" | optional | — |
clear | boolean | optional | — |
Responses
- 200Successful invocation
MailboxDismissOtherMailOutputField Type Required Description idstring · uuidrequired — dismissedAtstringrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/mailbox.listOverdueList every overdue notice of the CPA mailbox, whichever lane holds it (approve / input / active), deadline asc nulls last, then id — the same firm-wide population and overdue predicate laneSummary counts `overdueByLane` with. Unpaged; `capped` true means the population hit the row cap. Firm users only: an entity-scoped caller gets 403 MAILBOX_CPA_ONLY before any read; a firm whose CPA_MAILBOX flag is off gets 403 FEATURE_DISABLED. AUTONOMOUS — read-only.
List every overdue notice of the CPA mailbox, whichever lane holds it (approve / input / active), deadline asc nulls last, then id — the same firm-wide population and overdue predicate laneSummary counts `overdueByLane` with. Unpaged; `capped` true means the population hit the row cap. Firm users only: an entity-scoped caller gets 403 MAILBOX_CPA_ONLY before any read; a firm whose CPA_MAILBOX flag is off gets 403 FEATURE_DISABLED. AUTONOMOUS — read-only.
notices.readRequest body MailboxListOverdueInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string · uuid | optional | Narrow to the notices attached to this employer (entity id). An id that is not one of the firm's employers returns the empty slice. |
owner | enum: "mine" | "unassigned" | optional | 'mine' = notices the caller owns; 'unassigned' = OPEN notices nobody owns (the same set as the work queue's Unassigned count). Omitted = every owner. |
Responses
- 200Successful invocation
MailboxListOverdueOutputField Type Required Description rowsarray<object>required Every notice past its governing deadline (the Overdue chip's predicate) in the approve / input / active lanes, deadline asc nulls last, then id. A notice of a removed employer is not listed. cappedbooleanrequired — asOfstringrequired ISO-8601 instant of this read (one clock call per read). laneSummary and listLane are separate reads, each with its own asOf: a listLane total may drift from the summary's lane count when the firm changed in between — re-read laneSummary to reconcile. - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:mfa7
POST/v1/mfa.getMatrixRead the Kreto-curated MFA capability row for a portal: supported second-factor methods, the preferred waterfall rung, whether the portal's identity layer blocks VoIP numbers, and the Google-Voice canary state. Use when enrolling a credential to pick the right MFA strategy for that portal.
Read the Kreto-curated MFA capability row for a portal: supported second-factor methods, the preferred waterfall rung, whether the portal's identity layer blocks VoIP numbers, and the Google-Voice canary state. Use when enrolling a credential to pick the right MFA strategy for that portal.
admin.readRequest body MfaGetMatrixInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
agencyId | string | required | — |
Responses
- 200Successful invocation
MfaGetMatrixOutputField Type Required Description entryobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/mfa.resolveStrategyResolve which MFA rung the portal worker will drive for this account + portal: the account's per-portal override wins, else the curated matrix, else freeze_required (an unknown MFA method freezes the credential and requires a human — AC-13). A resolved 'sms' rung degrades to route_to_human while the relay is dormant.
Resolve which MFA rung the portal worker will drive for this account + portal: the account's per-portal override wins, else the curated matrix, else freeze_required (an unknown MFA method freezes the credential and requires a human — AC-13). A resolved 'sms' rung degrades to route_to_human while the relay is dormant.
admin.readRequest body MfaResolveStrategyInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
agencyId | string | required | — |
Responses
- 200Successful invocation
MfaResolveStrategyOutputField Type Required Description strategyone of (enum: "totp" | "email" | "sms" | "route_to_human" | … · enum: "freeze_required")required — sourceenum: "override" | "matrix" | "unresolved"required — voipBlockedbooleanrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/mfa.listHandoffsList the account's route-to-human MFA handoffs, newest first: any pending request (a portal session is waiting for a human to supply a code right now) plus the recent ledger (fulfilled / expired / escalated). Code values are never returned — codes are write-only.
List the account's route-to-human MFA handoffs, newest first: any pending request (a portal session is waiting for a human to supply a code right now) plus the recent ledger (fulfilled / expired / escalated). Code values are never returned — codes are write-only.
admin.readRequest body MfaListHandoffsInputrequired
application/json
Responses
- 200Successful invocation
MfaListHandoffsOutputField Type Required Description handoffsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/mfa.submitHandoffCodeSupply the MFA code a waiting portal session asked for (route-to-human). Fulfills the pending handoff so the portal worker can inject the code; refused once the session window has lapsed. UI-only.
Supply the MFA code a waiting portal session asked for (route-to-human). Fulfills the pending handoff so the portal worker can inject the code; refused once the session window has lapsed. UI-only.
admin.writeRequest body MfaSubmitHandoffCodeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
handoffId | string | required | — |
code | string | required | — |
Responses
- 200Successful invocation
MfaSubmitHandoffCodeOutputField Type Required Description fulfilledbooleanrequired — windowLapsedbooleanrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/mfa.listRelayNumbersList the account's leased receive-only relay numbers (one per credential, per the locked per-client model) and whether the SMS relay is currently enabled. Released numbers stay listed for the audit trail (AC-19).
List the account's leased receive-only relay numbers (one per credential, per the locked per-client model) and whether the SMS relay is currently enabled. Released numbers stay listed for the audit trail (AC-19).
admin.readRequest body MfaListRelayNumbersInputrequired
application/json
Responses
- 200Successful invocation
MfaListRelayNumbersOutputField Type Required Description numbersarray<object>required — smsEnabledbooleanrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/mfa.leaseRelayNumberLease a receive-only relay number for one credential (the SMS rung of the waterfall). Dormant: refuses with sms_disabled until OTP_SMS_ENABLED and a provider account exist. UI-only.
Lease a receive-only relay number for one credential (the SMS rung of the waterfall). Dormant: refuses with sms_disabled until OTP_SMS_ENABLED and a provider account exist. UI-only.
admin.writeRequest body MfaLeaseRelayNumberInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
credentialId | string | required | — |
Responses
- 200Successful invocation
MfaLeaseRelayNumberOutputField Type Required Description numberobjectrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/mfa.releaseRelayNumberRelease a leased relay number (AC-19: credential revocation releases its relay number; the row is kept for the audit trail). UI-only.
Release a leased relay number (AC-19: credential revocation releases its relay number; the row is kept for the audit trail). UI-only.
admin.writeRequest body MfaReleaseRelayNumberInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
numberId | string | required | — |
Responses
- 200Successful invocation
MfaReleaseRelayNumberOutputField Type Required Description releasedbooleanrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:notice-intake13
POST/v1/notice-intake.ingestEmailWrap the existing /api/webhooks/inbound-email logic into a typed service method. Slug-based routing → domain fallback → unmatched queue. AUTONOMOUS — system-driven, idempotent at the inbound_emails row level.
Wrap the existing /api/webhooks/inbound-email logic into a typed service method. Slug-based routing → domain fallback → unmatched queue. AUTONOMOUS — system-driven, idempotent at the inbound_emails row level.
notices.writeRequest body NoticeIntakeIngestEmailInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
resendPayload | object | required | — |
Responses
- 200Successful invocation
NoticeIntakeIngestEmailOutputField Type Required Description inboundEmailIdstring · uuidrequired — matchMethodenum: "slug" | "domain" | "none"required — matchedTenantIdstring · uuidrequired — matchedEntityIdstring · uuidrequired — unmatchedbooleanrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notice-intake.classifyNoticeWrap agentClassify with a typed service-method surface. Reads the notice's raw_text + state, calls the LLM classifier, persists notice_type_classified + classification_confidence + notice_facts. AUTONOMOUS — read-modify, recoverable.
Wrap agentClassify with a typed service-method surface. Reads the notice's raw_text + state, calls the LLM classifier, persists notice_type_classified + classification_confidence + notice_facts. AUTONOMOUS — read-modify, recoverable.
notices.readRequest body NoticeIntakeClassifyNoticeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
Responses
- 200Successful invocation
NoticeIntakeClassifyNoticeOutputField Type Required Description noticeIdstring · uuidrequired — noticeTypestringrequired — confidencenumberrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notice-intake.ingestNoticeWrap agentIngest with a typed service-method surface. Runs OCR / text-extraction (PDF text layer -> Claude Vision OCR) + SHA-256 dedup, persists raw_text + sha256_hash + pipeline_status. AUTONOMOUS — read-modify, recoverable.
Wrap agentIngest with a typed service-method surface. Runs OCR / text-extraction (PDF text layer -> Claude Vision OCR) + SHA-256 dedup, persists raw_text + sha256_hash + pipeline_status. AUTONOMOUS — read-modify, recoverable.
notices.writeRequest body NoticeIntakeIngestNoticeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
Responses
- 200Successful invocation
NoticeIntakeIngestNoticeOutputField Type Required Description noticeIdstring · uuidrequired — pipelineStatusstringrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notice-intake.validateNoticeWrap agentValidate with a typed service-method surface. Runs SUI / rate verification + payroll cross-reference, flags protest + potential savings, persists pipeline_status + protest_recommended. AUTONOMOUS — read-modify, recoverable.
Wrap agentValidate with a typed service-method surface. Runs SUI / rate verification + payroll cross-reference, flags protest + potential savings, persists pipeline_status + protest_recommended. AUTONOMOUS — read-modify, recoverable.
notices.readRequest body NoticeIntakeValidateNoticeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
Responses
- 200Successful invocation
NoticeIntakeValidateNoticeOutputField Type Required Description noticeIdstring · uuidrequired — pipelineStatusstringrequired — protestRecommendedbooleanrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notice-intake.setNoticeDeadlineWrap agentDeadline with a typed service-method surface. Computes the response deadline + protest clock from the notice's classification + per-state rules, persists deadline + protest_deadline + pipeline_status. AUTONOMOUS — read-modify, recoverable.
Wrap agentDeadline with a typed service-method surface. Computes the response deadline + protest clock from the notice's classification + per-state rules, persists deadline + protest_deadline + pipeline_status. AUTONOMOUS — read-modify, recoverable.
notices.writeRequest body NoticeIntakeSetNoticeDeadlineInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
Responses
- 200Successful invocation
NoticeIntakeSetNoticeDeadlineOutputField Type Required Description noticeIdstring · uuidrequired — pipelineStatusstringrequired — deadlinestringrequired — protestDeadlinestringrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notice-intake.reclassifyNoticeOperator-driven re-classification. Re-runs the classifier on the notice as it stands (its pipeline status is NOT reset). When the classifier's type differs from the stored one, the change is chained old → new and ONE continuation job is queued — the same job the CPA's set-type door queues — which re-runs the later steps in order and stops at the next human gate; the response names that job. Same type: nothing is queued. Refused (409 TYPE_CORRECTION_REFUSED, nothing written) once a filing, an outside response, a signature request or an approval is on record, or the notice is closed, resolved, dismissed, rejected, verified, agency-responded, SIDES-exempt, failed or OCR-only. NOTIFY — operator override, audit-visible. Reason field NOT redacted (operator-authored; if PII leaks, it's the operator's responsibility — flagged in README).
Operator-driven re-classification. Re-runs the classifier on the notice as it stands (its pipeline status is NOT reset). When the classifier's type differs from the stored one, the change is chained old → new and ONE continuation job is queued — the same job the CPA's set-type door queues — which re-runs the later steps in order and stops at the next human gate; the response names that job. Same type: nothing is queued. Refused (409 TYPE_CORRECTION_REFUSED, nothing written) once a filing, an outside response, a signature request or an approval is on record, or the notice is closed, resolved, dismissed, rejected, verified, agency-responded, SIDES-exempt, failed or OCR-only. NOTIFY — operator override, audit-visible. Reason field NOT redacted (operator-authored; if PII leaks, it's the operator's responsibility — flagged in README).
notices.writeRequest body NoticeIntakeReclassifyNoticeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
reason | string | required | — |
Responses
- 200Successful invocation
NoticeIntakeReclassifyNoticeOutputField Type Required Description noticeIdstring · uuidrequired — noticeTypestringrequired — confidencenumberrequired — cascadedAgentsarray<string>required — continuationobjectrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notice-intake.routeNoticeAssign a notice to an employer entity. Mirrors entity-match-resolve route's 'confirmed' action. Pure data update — does NOT advance pipeline_status, does NOT cascade to downstream agents. NOTIFY — operator override, audit-visible.
Assign a notice to an employer entity. Mirrors entity-match-resolve route's 'confirmed' action. Pure data update — does NOT advance pipeline_status, does NOT cascade to downstream agents. NOTIFY — operator override, audit-visible.
notices.writeRequest body NoticeIntakeRouteNoticeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
entityId | string · uuid | required | — |
Responses
- 200Successful invocation
NoticeIntakeRouteNoticeOutputField Type Required Description noticeIdstring · uuidrequired — previousEntityIdstring · uuidrequired — newEntityIdstring · uuidrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notice-intake.flagJunkMark a notice as junk. Sets is_junk=true + pipeline_status=DISMISSED. Refused (409 NOTICE_PAST_TRIAGE, nothing written) once the notice is approved, filed, actioned, resolved or dismissed. NOTIFY — recoverable (unflagJunk restores the prior pipeline_status); no compliance impact from a wrong call. Compliance-impact gate is at submitProtest (Phase 3).
Mark a notice as junk. Sets is_junk=true + pipeline_status=DISMISSED. Refused (409 NOTICE_PAST_TRIAGE, nothing written) once the notice is approved, filed, actioned, resolved or dismissed. NOTIFY — recoverable (unflagJunk restores the prior pipeline_status); no compliance impact from a wrong call. Compliance-impact gate is at submitProtest (Phase 3).
notices.writeRequest body NoticeIntakeFlagJunkInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
reason | enum: "marketing" | "portal_automation" | "bounce_notification" | "newsletter" | … | required | — |
reasonDetail | string | optional | — |
Responses
- 200Successful invocation
NoticeIntakeFlagJunkOutputField Type Required Description noticeIdstring · uuidrequired — isJunkenum: truerequired — pipelineStatusenum: "DISMISSED"required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notice-intake.unflagJunkReverse a junk flag. Sets is_junk=false and restores pipeline_status to its value before the flag (from the flag's audit before-image); PENDING when no flag audit row exists, reported as restoreSource=default_pending. Refused (409 NOTICE_NOT_JUNK) on a notice that is not flagged. NOTIFY — operator override; the reason field is the operator's audit-rationale (10–500 chars).
Reverse a junk flag. Sets is_junk=false and restores pipeline_status to its value before the flag (from the flag's audit before-image); PENDING when no flag audit row exists, reported as restoreSource=default_pending. Refused (409 NOTICE_NOT_JUNK) on a notice that is not flagged. NOTIFY — operator override; the reason field is the operator's audit-rationale (10–500 chars).
notices.writeRequest body NoticeIntakeUnflagJunkInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
reason | string | required | — |
Responses
- 200Successful invocation
NoticeIntakeUnflagJunkOutputField Type Required Description noticeIdstring · uuidrequired — isJunkenum: falserequired — pipelineStatusenum: "PENDING" | "INGESTED" | "CLASSIFIED" | "VALIDATED" | …required — restoreSourceenum: "flag_audit" | "default_pending"required — readingRequeuedbooleanrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notice-intake.draftNoticeWrap agentDraft with a typed service-method surface. Generates the protest-letter draft for the notice (persists the draft row + `protest_draft_generated_at`) and returns a typed acknowledgment. Advances `pipeline_status` to 'READY_FOR_REVIEW' UNLESS the payroll gate holds the notice at 'AWAITING_PAYROLL' — a notice that needs payroll evidence and has none keeps its phase and the draft is written BLOCKED. NOTIFY — read-modify, a human reviews the draft before any external send.
Wrap agentDraft with a typed service-method surface. Generates the protest-letter draft for the notice (persists the draft row + `protest_draft_generated_at`) and returns a typed acknowledgment. Advances `pipeline_status` to 'READY_FOR_REVIEW' UNLESS the payroll gate holds the notice at 'AWAITING_PAYROLL' — a notice that needs payroll evidence and has none keeps its phase and the draft is written BLOCKED. NOTIFY — read-modify, a human reviews the draft before any external send.
notices.writeRequest body NoticeIntakeDraftNoticeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
Responses
- 200Successful invocation
NoticeIntakeDraftNoticeOutputField Type Required Description noticeIdstring · uuidrequired — hasDraftbooleanrequired — draftLengthintegerrequired — pipelineStatusstringrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notice-intake.approveNoticeOperator marks the generated draft as approved + ready to file. Records approved_at + approved_by + cpa_action='approved'. Does NOT transition pipeline_status or send anything externally. NOTIFY — reversible; fileNotice is the external-impact gate.
Operator marks the generated draft as approved + ready to file. Records approved_at + approved_by + cpa_action='approved'. Does NOT transition pipeline_status or send anything externally. NOTIFY — reversible; fileNotice is the external-impact gate.
notices.writeRequest body NoticeIntakeApproveNoticeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
note | string | optional | — |
Responses
- 200Successful invocation
NoticeIntakeApproveNoticeOutputField Type Required Description noticeIdstring · uuidrequired — approvedAtstringrequired — cpaActionenum: "approved"required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notice-intake.fileNoticeOperator records that the approved response has been transmitted to the agency. Stores filed_at + filed_by + filing_method + confirmation_number, advances status to 'filed' (via the trigger-mandated two-step transition) and pipeline_status to ACTIONED. Requires an approval that is EVIDENCED on the notice's audit trail, not merely asserted by cpa_action (the approve-before-file invariant). NOTIFY — records external act; Kreto itself does NOT mail/fax/post in this op.
Operator records that the approved response has been transmitted to the agency. Stores filed_at + filed_by + filing_method + confirmation_number, advances status to 'filed' (via the trigger-mandated two-step transition) and pipeline_status to ACTIONED. Requires an approval that is EVIDENCED on the notice's audit trail, not merely asserted by cpa_action (the approve-before-file invariant). NOTIFY — records external act; Kreto itself does NOT mail/fax/post in this op.
notices.writeRequest body NoticeIntakeFileNoticeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
filingMethod | enum: "mail" | "fax" | "online_portal" | "email" | … | required | — |
note | string | optional | — |
confirmationNumber | string | optional | — |
Responses
- 200Successful invocation
NoticeIntakeFileNoticeOutputField Type Required Description noticeIdstring · uuidrequired — filedAtstringrequired — filingMethodenum: "mail" | "fax" | "online_portal" | "email" | …required — pipelineStatusenum: "ACTIONED"required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notice-intake.escalateNoticeFlag a notice for senior-CPA review. Sets is_escalated=true + escalated_at + escalated_by + escalation_reason. Does NOT change pipeline_status (escalation is orthogonal). Idempotent — re-calling updates the reason + timestamp. NOTIFY — flag-only; no external impact.
Flag a notice for senior-CPA review. Sets is_escalated=true + escalated_at + escalated_by + escalation_reason. Does NOT change pipeline_status (escalation is orthogonal). Idempotent — re-calling updates the reason + timestamp. NOTIFY — flag-only; no external impact.
notices.writeRequest body NoticeIntakeEscalateNoticeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
reason | string | required | — |
Responses
- 200Successful invocation
NoticeIntakeEscalateNoticeOutputField Type Required Description noticeIdstring · uuidrequired — isEscalatedenum: truerequired — escalatedAtstringrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:notices10
POST/v1/notices.submitDraftFeedbackRecord thumbs-up/down feedback on a notice response draft. Asserts the notice and draft both belong to the caller's tenant, inserts a draft_feedback row, and emits a product-analytics event. NOTIFY — operator-driven, audit-visible.
Record thumbs-up/down feedback on a notice response draft. Asserts the notice and draft both belong to the caller's tenant, inserts a draft_feedback row, and emits a product-analytics event. NOTIFY — operator-driven, audit-visible.
notices.writeRequest body NoticesSubmitDraftFeedbackInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
draftId | string · uuid | required | — |
rating | enum: "thumbs_up" | "thumbs_down" | required | — |
comment | string | optional | — |
Responses
- 200Successful invocation
NoticesSubmitDraftFeedbackOutputField Type Required Description successenum: truerequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notices.resolveEntityMatchResolve a notice's fuzzy entity match: confirm an existing entity, or create a new entity under the notice's tenant and assign it. Tenant-scoped with defense-in-depth assertions. NOTIFY — operator-driven, audit-visible.
Resolve a notice's fuzzy entity match: confirm an existing entity, or create a new entity under the notice's tenant and assign it. Tenant-scoped with defense-in-depth assertions. NOTIFY — operator-driven, audit-visible.
notices.writeRequest body NoticesResolveEntityMatchInputrequired
application/json
Responses
- 200Successful invocation
NoticesResolveEntityMatchOutputField Type Required Description successenum: truerequired — entityIdstring · uuidrequired — actionenum: "confirmed" | "create_new"required — entityNamestringoptional — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notices.linkLink an agency-response notice to its parent (original) notice and record the response type (acceptance/counter/additional_info_request/rejection). Sets parent_notice_id + agency_response_type on the response notice. Both notices must belong to the caller's tenant. AUTONOMOUS — internal edge write, recoverable, no agency impact.
Link an agency-response notice to its parent (original) notice and record the response type (acceptance/counter/additional_info_request/rejection). Sets parent_notice_id + agency_response_type on the response notice. Both notices must belong to the caller's tenant. AUTONOMOUS — internal edge write, recoverable, no agency impact.
notices.writeRequest body NoticesLinkInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
parentNoticeId | string · uuid | required | — |
agencyResponseType | enum: "acceptance" | "counter" | "additional_info_request" | "rejection" | required | — |
Responses
- 200Successful invocation
NoticesLinkOutputField Type Required Description successenum: truerequired — noticeIdstring · uuidrequired — parentNoticeIdstring · uuidrequired — agencyResponseTypeenum: "acceptance" | "counter" | "additional_info_request" | "rejection"required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notices.deadlineSweepPreview the deadline escalations that would fire for your tenant — which compliance-notice deadlines are escalating and who would be notified. Read-only plan over the same ladder + candidate filter the hourly cron uses; sends nothing and writes nothing (the cron does the actual emails/push/Slack). Use to surface what deadlines need attention now. NOTIFY — surfaces work for human attention, no external impact.
Preview the deadline escalations that would fire for your tenant — which compliance-notice deadlines are escalating and who would be notified. Read-only plan over the same ladder + candidate filter the hourly cron uses; sends nothing and writes nothing (the cron does the actual emails/push/Slack). Use to surface what deadlines need attention now. NOTIFY — surfaces work for human attention, no external impact.
notices.writeRequest body NoticesDeadlineSweepInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string · uuid | optional | — |
horizonDays | integer | optional | — |
Responses
- 200Successful invocation
NoticesDeadlineSweepOutputField Type Required Description asOfstringrequired — scannedNoticesintegerrequired — escalationsarray<object>required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notices.crossReferenceQueue a cross-reference of a notice against the employer's payroll (re-runs the discrepancy measurement). Refused with 409 ENTITY_UNCONFIRMED while the notice's employer is unconfirmed. Idempotent per notice per day. Tenant- and employer-scoped. NOTIFY — recomputation on the job queue, audited, no agency impact.
Queue a cross-reference of a notice against the employer's payroll (re-runs the discrepancy measurement). Refused with 409 ENTITY_UNCONFIRMED while the notice's employer is unconfirmed. Idempotent per notice per day. Tenant- and employer-scoped. NOTIFY — recomputation on the job queue, audited, no agency impact.
notices.writeRequest body NoticesCrossReferenceInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
Responses
- 200Successful invocation
NoticesCrossReferenceOutputField Type Required Description enqueuedenum: truerequired — noticeIdstring · uuidrequired — jobIdstring · uuidrequired — deduplicatedbooleanrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notices.getDecisionCardRead a notice's decision card: four rows (Read · Checked · Proposed · Decision), each with a state, labelled figures and a link to the page that holds the evidence. Read-only — approve, decline and file stay at their existing doors. Tenant- and employer-scoped; an employer caller gets the employer projection. AUTONOMOUS.
Read a notice's decision card: four rows (Read · Checked · Proposed · Decision), each with a state, labelled figures and a link to the page that holds the evidence. Read-only — approve, decline and file stay at their existing doors. Tenant- and employer-scoped; an employer caller gets the employer projection. AUTONOMOUS.
notices.readRequest body NoticesGetDecisionCardInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
Responses
- 200Successful invocation
NoticesGetDecisionCardOutputField Type Required Description noticeIdstring · uuidrequired — planeenum: "cpa" | "employer"required — currentenum: "read" | "checked" | "proposed" | "decision"required — figureHeldbooleanrequired — rowsobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notices.getExperienceRequestRead a notice's open experience-data request — the experience-statement figures the employer is asked for, the tax year and the state — and whether the caller may answer it (only the employer's owner). `request` is null when none is open. Tenant- and employer-scoped; read-only. AUTONOMOUS.
Read a notice's open experience-data request — the experience-statement figures the employer is asked for, the tax year and the state — and whether the caller may answer it (only the employer's owner). `request` is null when none is open. Tenant- and employer-scoped; read-only. AUTONOMOUS.
notices.readRequest body NoticesGetExperienceRequestInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
Responses
- 200Successful invocation
NoticesGetExperienceRequestOutputField Type Required Description requestobjectrequired — canAnswerbooleanrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notices.submitExperienceFiguresThe employer's owner states the experience-statement figures an open experience-data request asks for (only the asked keys; average annual payroll above 0). Stored as employer-stated notice facts — never as a Kreto-verified figure — then the rate decision re-runs through the job queue. Replaying the same answer is deduplicated; a different answer to an answered request is refused (409). Owner-only, tenant- and employer-scoped. NOTIFY — the CPA sees the result.
The employer's owner states the experience-statement figures an open experience-data request asks for (only the asked keys; average annual payroll above 0). Stored as employer-stated notice facts — never as a Kreto-verified figure — then the rate decision re-runs through the job queue. Replaying the same answer is deduplicated; a different answer to an answered request is refused (409). Owner-only, tenant- and employer-scoped. NOTIFY — the CPA sees the result.
notices.writeRequest body NoticesSubmitExperienceFiguresInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
requestId | string | required | — |
figures | object | required | — |
Responses
- 200Successful invocation
NoticesSubmitExperienceFiguresOutputField Type Required Description answeredenum: truerequired — requestIdstringrequired — deduplicatedbooleanrequired — rerunobjectrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notices.confirmRateWithAgencyClose a range-held SUI rate notice (the rate is outside Kreto's recorded range for the state and no protest is recommended) because the agency confirmed the rate: no letter is sent, the notice closes as 'Confirmed with the agency' through the lifecycle writer, and the close is recorded on the audit chain with the confirming user. Refused (409) when the notice is not range-held, already closed, or has an approved protest. A repeated call on a confirmed notice replays. Tenant- and employer-scoped; a person must call it. NOTIFY.
Close a range-held SUI rate notice (the rate is outside Kreto's recorded range for the state and no protest is recommended) because the agency confirmed the rate: no letter is sent, the notice closes as 'Confirmed with the agency' through the lifecycle writer, and the close is recorded on the audit chain with the confirming user. Refused (409) when the notice is not range-held, already closed, or has an approved protest. A repeated call on a confirmed notice replays. Tenant- and employer-scoped; a person must call it. NOTIFY.
notices.writeRequest body NoticesConfirmRateWithAgencyInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
Responses
- 200Successful invocation
NoticesConfirmRateWithAgencyOutputField Type Required Description closedenum: truerequired — replayedbooleanrequired — verdictenum: "agency_confirmed"required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notices.requestExperienceFiguresAsk the employer's owner for the experience-statement figures that check an in-range SUI rate increase Kreto could only check against the state's range. Opens the same experience-data request Kreto fires on its own (one email to the employer's owners, the request on the employer's notice). Idempotent: an open request is returned, never re-sent. Refused (409) when the figures are already on file, when the rate does not need them, or when the employer has no owner account. CPA only; tenant- and employer-scoped. NOTIFY.
Ask the employer's owner for the experience-statement figures that check an in-range SUI rate increase Kreto could only check against the state's range. Opens the same experience-data request Kreto fires on its own (one email to the employer's owners, the request on the employer's notice). Idempotent: an open request is returned, never re-sent. Refused (409) when the figures are already on file, when the rate does not need them, or when the employer has no owner account. CPA only; tenant- and employer-scoped. NOTIFY.
notices.writeRequest body NoticesRequestExperienceFiguresInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
Responses
- 200Successful invocation
NoticesRequestExperienceFiguresOutputField Type Required Description requestedenum: truerequired — deduplicatedbooleanrequired — requestobjectrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:notifications2
POST/v1/notifications.listList the account's vault notifications, newest first: pending time-critical items (awaiting a human acknowledgement) and the informational ledger (rotations completed, credentials frozen, health changes). Content is reference-and-action only — never a secret, code, or amount.
List the account's vault notifications, newest first: pending time-critical items (awaiting a human acknowledgement) and the informational ledger (rotations completed, credentials frozen, health changes). Content is reference-and-action only — never a secret, code, or amount.
admin.readRequest body NotificationsListInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
status | enum: "pending" | "acknowledged" | "expired" | optional | — |
Responses
- 200Successful invocation
NotificationsListOutputField Type Required Description notificationsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/notifications.ackAcknowledge a time-critical notification, stopping its escalation. Escalate-until-ack means until a HUMAN acks — UI-only by design.
Acknowledge a time-critical notification, stopping its escalation. Escalate-until-ack means until a HUMAN acks — UI-only by design.
admin.writeRequest body NotificationsAckInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
notificationId | string | required | — |
Responses
- 200Successful invocation
NotificationsAckOutputField Type Required Description acknowledgedbooleanrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:payments5
POST/v1/payments.remitRequest a payment from a stored payment/bank credential. Creates an approval request ONLY — money never moves without a human approving this exact request (the hard floor no configuration can relax). The responsible humans are notified WITHOUT the amount; they first see the figure on the approval screen.
Request a payment from a stored payment/bank credential. Creates an approval request ONLY — money never moves without a human approving this exact request (the hard floor no configuration can relax). The responsible humans are notified WITHOUT the amount; they first see the figure on the approval screen.
admin.writeRequest body PaymentsRemitInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
credentialId | string | required | — |
payee | string | required | — |
amountCents | integer | required | — |
currency | string | optional | — |
memo | string | optional | — |
Responses
- 200Successful invocation
PaymentsRemitOutputField Type Required Description requestobjectrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/payments.listList the account's payment approval requests, newest first. Use to surface what is awaiting human approval; approving is UI-only.
List the account's payment approval requests, newest first. Use to surface what is awaiting human approval; approving is UI-only.
admin.readRequest body PaymentsListInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
status | enum: "requested" | "approved" | "rejected" | "expired" | optional | — |
Responses
- 200Successful invocation
PaymentsListOutputField Type Required Description requestsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/payments.approveApprove a pending payment request — the money-movement hard floor. Requires a human actor and a typed amount that matches the request exactly; the approval (approver identity + amount) is sealed into the tamper-evident audit chain and becomes the authorization. UI-only; no configuration can bypass this step.
Approve a pending payment request — the money-movement hard floor. Requires a human actor and a typed amount that matches the request exactly; the approval (approver identity + amount) is sealed into the tamper-evident audit chain and becomes the authorization. UI-only; no configuration can bypass this step.
admin.writeRequest body PaymentsApproveInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
approvalId | string | required | — |
confirmAmountCents | integer | required | — |
Responses
- 200Successful invocation
PaymentsApproveOutputField Type Required Description requestobjectrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/payments.rejectReject a pending payment request. The rejection is recorded on the audit chain. UI-only.
Reject a pending payment request. The rejection is recorded on the audit chain. UI-only.
admin.writeRequest body PaymentsRejectInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
approvalId | string | required | — |
Responses
- 200Successful invocation
PaymentsRejectOutputField Type Required Description requestobjectrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/payments.executeExecute an APPROVED payment. DORMANT: Kreto has no payment rails — this refuses payment_rails_unavailable for every request, approved or not. When an ACH/provider integration lands it slots in here; the approval floor above it does not change. UI-only.
Execute an APPROVED payment. DORMANT: Kreto has no payment rails — this refuses payment_rails_unavailable for every request, approved or not. When an ACH/provider integration lands it slots in here; the approval floor above it does not change. UI-only.
admin.writeRequest body PaymentsExecuteInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
approvalId | string | required | — |
Responses
- 200Successful invocation
PaymentsExecuteOutputField Type Required Description executedenum: falserequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:payroll4
POST/v1/payroll.connectInitiate a payroll connection for an entity. Returns 'connected' (already linked), 'consent_required' (the employer must accept the KRT-768 consent in the UI first — never recorded on their behalf), or 'pending' (consent present; a pending connection intent is recorded and the employer completes the provider OAuth in the UI). Use to unblock a notice that needs payroll. NOTIFY — records the intent; the actual link is the employer's OAuth action.
Initiate a payroll connection for an entity. Returns 'connected' (already linked), 'consent_required' (the employer must accept the KRT-768 consent in the UI first — never recorded on their behalf), or 'pending' (consent present; a pending connection intent is recorded and the employer completes the provider OAuth in the UI). Use to unblock a notice that needs payroll. NOTIFY — records the intent; the actual link is the employer's OAuth action.
payroll.writeRequest body PayrollConnectInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string · uuid | required | — |
provider | enum: "gusto" | "adp_run" | "paychex" | "paylocity" | … | required | — |
Responses
- 200Successful invocation
PayrollConnectOutputField Type Required Description entityIdstring · uuidrequired — providerenum: "gusto" | "adp_run" | "paychex" | "paylocity" | …required — statusenum: "connected" | "consent_required" | "pending"required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/payroll.gateStatusAnswer 'what is Kreto waiting on for this notice?' — the AWAITING_PAYROLL gate's state for one notice, with NO side effect. Returns the same discriminants payroll.releaseGate uses ('released' = the gate would open now, 'needs_connection' = no active payroll connection, 'needs_input' = connected but the notice's state + tax year are not covered yet, 'not_gated' = the notice is not held at the payroll gate, 'unreadable' = the payroll on file could not be read just now; this is not an answer about whether payroll is on file), plus whether a connection is active, the tax year coverage was checked for, and the named coverage gap when the probe could name one. Both the employer panel and the release itself resolve through this same verdict, so the banner and the decision cannot disagree. AUTONOMOUS — pure read. Refuses with NOTICE_NOT_FOUND (404) for unknown, other-tenant or out-of-scope notices and FEATURE_DISABLED (403) when FF_PAYROLL_GATE_AUTO_RELEASE is off.
Answer 'what is Kreto waiting on for this notice?' — the AWAITING_PAYROLL gate's state for one notice, with NO side effect. Returns the same discriminants payroll.releaseGate uses ('released' = the gate would open now, 'needs_connection' = no active payroll connection, 'needs_input' = connected but the notice's state + tax year are not covered yet, 'not_gated' = the notice is not held at the payroll gate, 'unreadable' = the payroll on file could not be read just now; this is not an answer about whether payroll is on file), plus whether a connection is active, the tax year coverage was checked for, and the named coverage gap when the probe could name one. Both the employer panel and the release itself resolve through this same verdict, so the banner and the decision cannot disagree. AUTONOMOUS — pure read. Refuses with NOTICE_NOT_FOUND (404) for unknown, other-tenant or out-of-scope notices and FEATURE_DISABLED (403) when FF_PAYROLL_GATE_AUTO_RELEASE is off.
payroll.readRequest body PayrollGateStatusInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
Responses
- 200Successful invocation
PayrollGateStatusOutputField Type Required Description noticeIdstring · uuidrequired — entityIdstring · uuidrequired — reasonenum: "released" | "needs_connection" | "needs_input" | "not_gated" | …required — gatedbooleanrequired — releasablebooleanrequired — connectedbooleanrequired — payrollOnFilebooleanrequired — hasStoredPayrollbooleanrequired — payrollReadOkbooleanrequired — providerstringrequired — coverageYearintegerrequired — coveragePeriodstringrequired — coverageGapstringrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/payroll.listGatedNoticesList an employer entity's notices currently held at the AWAITING_PAYROLL gate, each with its own gate verdict ('released' | 'needs_connection' | 'needs_input' | 'unreadable' — 'unreadable' = the payroll on file could not be read just now; this is not an answer about whether payroll is on file), the tax year checked and any named coverage gap — the 'Notices waiting on this payroll (N)' list on the employer's payroll page. Also reports whether an active payroll connection exists for the entity. Capped at 20 notices per call (`truncated` says when there are more); the remainder release on the entity's next payroll sync. AUTONOMOUS — pure read. Refuses with ENTITY_NOT_FOUND (404) for entities outside the caller's scope and FEATURE_DISABLED (403) when FF_PAYROLL_GATE_AUTO_RELEASE is off.
List an employer entity's notices currently held at the AWAITING_PAYROLL gate, each with its own gate verdict ('released' | 'needs_connection' | 'needs_input' | 'unreadable' — 'unreadable' = the payroll on file could not be read just now; this is not an answer about whether payroll is on file), the tax year checked and any named coverage gap — the 'Notices waiting on this payroll (N)' list on the employer's payroll page. Also reports whether an active payroll connection exists for the entity. Capped at 20 notices per call (`truncated` says when there are more); the remainder release on the entity's next payroll sync. AUTONOMOUS — pure read. Refuses with ENTITY_NOT_FOUND (404) for entities outside the caller's scope and FEATURE_DISABLED (403) when FF_PAYROLL_GATE_AUTO_RELEASE is off.
payroll.readRequest body PayrollListGatedNoticesInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string · uuid | required | — |
Responses
- 200Successful invocation
PayrollListGatedNoticesOutputField Type Required Description entityIdstring · uuidrequired — connectedbooleanrequired — payrollOnFilebooleanrequired — payrollReadOkbooleanrequired — providerstringrequired — noticesarray<object>required — countintegerrequired — truncatedbooleanrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/payroll.releaseGateRelease the AWAITING_PAYROLL gate on one notice using payroll already on file — the employer's explicit 'Use my connected payroll' action, for an employer who connected long ago and has no sync event to ride. Probes the entity's payroll connection for real, re-runs the SAME coverage verdict the worker uses, and on 'released' enqueues the shared PROCESS_PAYROLL_VERIFICATION job and appends a hash-chained payroll_gate_released event naming the actor and trigger. Idempotent: a second call inside the same minute coalesces onto the first call's job and returns the same jobId with deduplicated=true and no second audit row. Writes NO pipeline_status — the transition out of AWAITING_PAYROLL stays the property of the verification handler. NOTIFY — it acts and then tells the CPA; it is not a sign-off, and it cannot open a gate the evidence does not open. Refuses with PAYROLL_GATE_NOT_GATED (409) when the notice is not at the gate, PAYROLL_GATE_NEEDS_CONNECTION / PAYROLL_GATE_NEEDS_INPUT (422) when the payroll evidence is missing, PAYROLL_GATE_UNREADABLE (409) when the payroll on file could not be read just now, NOTICE_NOT_FOUND (404) for unknown, other-tenant or out-of-scope notices, and FEATURE_DISABLED (403) when FF_PAYROLL_GATE_AUTO_RELEASE is off.
Release the AWAITING_PAYROLL gate on one notice using payroll already on file — the employer's explicit 'Use my connected payroll' action, for an employer who connected long ago and has no sync event to ride. Probes the entity's payroll connection for real, re-runs the SAME coverage verdict the worker uses, and on 'released' enqueues the shared PROCESS_PAYROLL_VERIFICATION job and appends a hash-chained payroll_gate_released event naming the actor and trigger. Idempotent: a second call inside the same minute coalesces onto the first call's job and returns the same jobId with deduplicated=true and no second audit row. Writes NO pipeline_status — the transition out of AWAITING_PAYROLL stays the property of the verification handler. NOTIFY — it acts and then tells the CPA; it is not a sign-off, and it cannot open a gate the evidence does not open. Refuses with PAYROLL_GATE_NOT_GATED (409) when the notice is not at the gate, PAYROLL_GATE_NEEDS_CONNECTION / PAYROLL_GATE_NEEDS_INPUT (422) when the payroll evidence is missing, PAYROLL_GATE_UNREADABLE (409) when the payroll on file could not be read just now, NOTICE_NOT_FOUND (404) for unknown, other-tenant or out-of-scope notices, and FEATURE_DISABLED (403) when FF_PAYROLL_GATE_AUTO_RELEASE is off.
payroll.writeRequest body PayrollReleaseGateInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
Responses
- 200Successful invocation
PayrollReleaseGateOutputField Type Required Description noticeIdstring · uuidrequired — entityIdstring · uuidrequired — releasedbooleanrequired — reasonenum: "released" | "needs_connection" | "needs_input" | "not_gated" | …required — jobIdstringrequired — deduplicatedbooleanrequired — coverageYearintegerrequired — coverageGapstringrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `payroll.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:poa-filing3
POST/v1/poa-filing.generate8821Generate an IRS Form 8821 (Tax Information Authorization) naming Kreto Inc. as the taxpayer's information designee. Fills the official IRS PDF template with the taxpayer's entity data and selected tax matters / years, uploads the unsigned PDF to Supabase Storage, creates a draft poa_authorizations row and a signature_requests row, returns the signing URL. The taxpayer (authorized signer on the entity) signs via the existing signing flow; finalization writes the signed PDF URL back to the poa_authorizations row.
Generate an IRS Form 8821 (Tax Information Authorization) naming Kreto Inc. as the taxpayer's information designee. Fills the official IRS PDF template with the taxpayer's entity data and selected tax matters / years, uploads the unsigned PDF to Supabase Storage, creates a draft poa_authorizations row and a signature_requests row, returns the signing URL. The taxpayer (authorized signer on the entity) signs via the existing signing flow; finalization writes the signed PDF URL back to the poa_authorizations row.
state_registration.writeRequest body PoaFilingGenerate8821Inputrequired
| Field | Type | Required | Description |
|---|---|---|---|
employerEntityId | string | required | — |
taxMatters | array<enum: "income" | "employment" | "excise" | "estate" | …> | required | — |
taxYears | array<integer> | required | — |
signerEmail | string · email | optional | — |
signerTitle | string | optional | — |
Responses
- 200Successful invocation
PoaFilingGenerate8821OutputField Type Required Description poaAuthorizationIdstringrequired — signatureRequestIdstringrequired — signingUrlstringrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/poa-filing.listPendingFilingsAdmin-only: list signed IRS Form 8821 POAs awaiting filing or in post-filing IRS states. Cross-tenant (Kreto staff console). Returns submission_status, signer info, filing_date, and CAF number for each row. Use to triage what 8821s are ready to upload to irs.gov/Submit8821, or to audit recent filings.
Admin-only: list signed IRS Form 8821 POAs awaiting filing or in post-filing IRS states. Cross-tenant (Kreto staff console). Returns submission_status, signer info, filing_date, and CAF number for each row. Use to triage what 8821s are ready to upload to irs.gov/Submit8821, or to audit recent filings.
admin.readRequest body PoaFilingListPendingFilingsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
status | enum: "signed" | "submitted" | "accepted" | "rejected" | optional | Filter by submission_status. Omit to receive every non-draft row. |
limit | integer | optional | — |
offset | integer | optional | — |
Responses
- 200Successful invocation
PoaFilingListPendingFilingsOutputField Type Required Description rowsarray<object>required — totalintegerrequired — - 400Validation error (input schema rejected). risk_level=elevated.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/poa-filing.transitionPoaFilingStatusAdmin-only: advance the IRS 8821 filing state machine for a single POA. Allowed: signed→submitted (Kreto staff uploaded to IRS portal), submitted→accepted (record CAF number from IRS ack), submitted→ rejected (record IRS rejection reason). Any other transition errors. Use to mirror IRS portal interactions back into Kreto state.
Admin-only: advance the IRS 8821 filing state machine for a single POA. Allowed: signed→submitted (Kreto staff uploaded to IRS portal), submitted→accepted (record CAF number from IRS ack), submitted→ rejected (record IRS rejection reason). Any other transition errors. Use to mirror IRS portal interactions back into Kreto state.
admin.writeRequest body PoaFilingTransitionPoaFilingStatusInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
poaAuthorizationId | string | required | — |
toStatus | enum: "submitted" | "accepted" | "rejected" | required | — |
cafNumber | string | optional | — |
rejectionReason | string | optional | — |
Responses
- 200Successful invocation
PoaFilingTransitionPoaFilingStatusOutputField Type Required Description poaAuthorizationIdstringrequired — submissionStatusenum: "signed" | "submitted" | "accepted" | "rejected" | …required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:policy2
POST/v1/policy.getGet the mailbox automation policy for the caller's tenant (or a specific entity): the per-action execution-mode matrix + step-up threshold + outbound posture, and the deadline-escalation ladder. Returns conservative code defaults (isDefault=true) when no policy row exists. AUTONOMOUS — read-only.
Get the mailbox automation policy for the caller's tenant (or a specific entity): the per-action execution-mode matrix + step-up threshold + outbound posture, and the deadline-escalation ladder. Returns conservative code defaults (isDefault=true) when no policy row exists. AUTONOMOUS — read-only.
admin.readRequest body PolicyGetInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string · uuid | optional | — |
Responses
- 200Successful invocation
PolicyGetOutputField Type Required Description policyobjectrequired — isDefaultbooleanrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/policy.updateSet the mailbox automation policy for the caller's tenant (or a specific entity): the per-action execution-mode matrix + step-up threshold + outbound posture, and the deadline-escalation ladder. Upserts the single (tenant, entity) row and seals an audit event. NOTIFY — a human-made control-plane change; it never auto-executes anything itself.
Set the mailbox automation policy for the caller's tenant (or a specific entity): the per-action execution-mode matrix + step-up threshold + outbound posture, and the deadline-escalation ladder. Upserts the single (tenant, entity) row and seals an audit event. NOTIFY — a human-made control-plane change; it never auto-executes anything itself.
admin.writeRequest body PolicyUpdateInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string · uuid | optional | — |
byActionType | object | required | — |
escalation | object | required | — |
Responses
- 200Successful invocation
PolicyUpdateOutputField Type Required Description policyobjectrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:portal-fetch3
POST/v1/portal-fetch.requestAsk Kreto to download an employer's documents (rate notices, benefit-charge statements, agency letters) from a state agency portal. Runs the one eligibility predicate (flag, vault lock, firm + employer consent, state authorization, login, MFA path, portal config, terms, cooldown) and, only when eligible, queues one portal_fetch run (single attempt, one per employer + agency + day). A refusal returns its reason code and sentence and writes nothing. NOTIFY: no portal settings are ever changed.
Ask Kreto to download an employer's documents (rate notices, benefit-charge statements, agency letters) from a state agency portal. Runs the one eligibility predicate (flag, vault lock, firm + employer consent, state authorization, login, MFA path, portal config, terms, cooldown) and, only when eligible, queues one portal_fetch run (single attempt, one per employer + agency + day). A refusal returns its reason code and sentence and writes nothing. NOTIFY: no portal settings are ever changed.
admin.writeRequest body PortalFetchRequestInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string · uuid | required | — |
agencyId | string | required | — |
kinds | array<enum: "rate_notice" | "benefit_charge_statement" | "correspondence"> | optional | — |
Responses
- 200Successful invocation
PortalFetchRequestOutput - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/portal-fetch.listList the agency-portal downloads Kreto ran for one stored login (`credentialId`) or one employer (`entityId`) — name exactly one — newest first: the status word, when it ran and how many new documents it found. The accountant also sees the trigger, the kinds asked for and the sentence that says what to do next; the employer sees dates and kinds only. Never a document title or a storage path. Tenant- and employer-scoped; read-only. AUTONOMOUS.
List the agency-portal downloads Kreto ran for one stored login (`credentialId`) or one employer (`entityId`) — name exactly one — newest first: the status word, when it ran and how many new documents it found. The accountant also sees the trigger, the kinds asked for and the sentence that says what to do next; the employer sees dates and kinds only. Never a document title or a storage path. Tenant- and employer-scoped; read-only. AUTONOMOUS.
admin.readRequest body PortalFetchListInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
credentialId | string · uuid | optional | — |
entityId | string · uuid | optional | — |
Responses
- 200Successful invocation
PortalFetchListOutputField Type Required Description planeenum: "cpa" | "employer"required — credentialobjectrequired — runsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/portal-fetch.getRead one agency-portal download and its files: the status word and, for the accountant, the next step (a person's step when the portal needed one, the login sentence when sign-in failed, the reason when Kreto stopped), and per file its kind, date, where Kreto put it and the notice or document it became. The employer sees dates and kinds only. Never a document title or a storage path. Tenant- and employer-scoped; read-only. AUTONOMOUS.
Read one agency-portal download and its files: the status word and, for the accountant, the next step (a person's step when the portal needed one, the login sentence when sign-in failed, the reason when Kreto stopped), and per file its kind, date, where Kreto put it and the notice or document it became. The employer sees dates and kinds only. Never a document title or a storage path. Tenant- and employer-scoped; read-only. AUTONOMOUS.
admin.readRequest body PortalFetchGetInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
fetchId | string · uuid | required | — |
Responses
- 200Successful invocation
PortalFetchGetOutputField Type Required Description planeenum: "cpa" | "employer"required — runobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:regulation4
POST/v1/regulation.getRuleThe one live regulation-catalog value for (family, key, jurisdiction) on an as-of date, with its rule id, status, checked-by level and source quote — or {withheld: reason} (no_rule, ambiguous, enum:<code>). No fallback, no 'latest', no neighbouring year; proposals and superseded rows are never returned. Global reference data, identical for every tenant. Example: SUI_PROTEST_WINDOW / SUI_RATE_NOTICE / AZ / 2026-03-02.
The one live regulation-catalog value for (family, key, jurisdiction) on an as-of date, with its rule id, status, checked-by level and source quote — or {withheld: reason} (no_rule, ambiguous, enum:<code>). No fallback, no 'latest', no neighbouring year; proposals and superseded rows are never returned. Global reference data, identical for every tenant. Example: SUI_PROTEST_WINDOW / SUI_RATE_NOTICE / AZ / 2026-03-02.
admin.readRequest body RegulationGetRuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
family | string | required | — |
key | string | required | — |
jurisdiction | one of (string · object) | required | — |
asOf | string | required | — |
attributes | object | optional | — |
Responses
- 200Successful invocation
RegulationGetRuleOutput - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/regulation.listProposalsPending regulation-catalog proposals (oldest first), optionally one family: the old/new value inputs a reviewer decides on, with the source quote and link. Read-only; Kreto staff (system_admin session) only.
Pending regulation-catalog proposals (oldest first), optionally one family: the old/new value inputs a reviewer decides on, with the source quote and link. Read-only; Kreto staff (system_admin session) only.
admin.readRequest body RegulationListProposalsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
family | string | optional | — |
limit | integer | optional | — |
Responses
- 200Successful invocation
RegulationListProposalsOutputField Type Required Description proposalsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/regulation.proposeRegulationRulePut ONE candidate regulation-catalog value (a contract proposal row) in the human review queue: writes a single state_compliance_obligation_proposals row with decision='pending' and NEVER writes a rule row — only a human decision (decideRegulationProposal) can make a rule live. Global reference data, no tenant filter, no employer PII. NOTIFY: agents, watchers and vendor adapters may call it; Kreto staff may call it as proposed_by_kind='staff'.
Put ONE candidate regulation-catalog value (a contract proposal row) in the human review queue: writes a single state_compliance_obligation_proposals row with decision='pending' and NEVER writes a rule row — only a human decision (decideRegulationProposal) can make a rule live. Global reference data, no tenant filter, no employer PII. NOTIFY: agents, watchers and vendor adapters may call it; Kreto staff may call it as proposed_by_kind='staff'.
admin.writeRequest body RegulationProposeRegulationRuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
rule_family | string | required | — |
rule_key | string | required | — |
jurisdiction_level | enum: "federal" | "state" | "county" | "city" | … | required | — |
state_code | string | required | — |
jurisdiction_name | string | required | — |
value_type | enum: "number" | "percent" | "days" | "money" | … | required | — |
value | object | required | — |
applies_to | object | required | — |
effective_from | string | required | — |
proposed_by_kind | enum: "watcher" | "cpa_sheet_import" | "vendor_adapter" | "legislation_watch" | … | required | — |
proposal_type | enum: "value" | "watch" | "level_raise" | required | — |
source_kind | string | required | — |
source_url | string · uri | required | — |
source_quote | string | required | — |
source_hash | string | required | — |
excerpt | string | required | — |
supersedes_rule_id | string | required | — |
impact | object | required | — |
Responses
- 200Successful invocation
RegulationProposeRegulationRuleOutputField Type Required Description proposalIdstringrequired — obligationCodestringrequired — decisionenum: "pending"required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/regulation.decideRegulationProposalHuman gate: approve or reject ONE pending regulation-catalog proposal with a named approver and a reason. Approval of a value proposal makes the new rule live and closes (or, for a same-period correction, supersedes) the row it replaces in one transaction. APPROVAL_REQUIRED, UI-only — excluded from MCP/CLI; refuses any non-human principal, any non-system_admin session, and any approver whose class may not decide the family (deadline + money families: CPA or owner; others: staff).
Human gate: approve or reject ONE pending regulation-catalog proposal with a named approver and a reason. Approval of a value proposal makes the new rule live and closes (or, for a same-period correction, supersedes) the row it replaces in one transaction. APPROVAL_REQUIRED, UI-only — excluded from MCP/CLI; refuses any non-human principal, any non-system_admin session, and any approver whose class may not decide the family (deadline + money families: CPA or owner; others: staff).
admin.writeRequest body RegulationDecideRegulationProposalInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
proposalId | string | required | — |
decision | enum: "approved" | "rejected" | required | — |
reason | string | required | — |
Responses
- 200Successful invocation
RegulationDecideRegulationProposalOutputField Type Required Description proposalIdstringrequired — decisionenum: "approved" | "rejected"required — proposalTypestringrequired — ruleIdstringrequired — supersededRuleIdstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:resolutions11
POST/v1/resolutions.getGet a single resolution draft by id for the caller's tenant. Returns the draft letter, status, and the agentic resolution metadata (needs_input/needs_connection/decision_context/trace). Tenant-scoped with a defense-in-depth cross-tenant assertion. AUTONOMOUS — read-only.
Get a single resolution draft by id for the caller's tenant. Returns the draft letter, status, and the agentic resolution metadata (needs_input/needs_connection/decision_context/trace). Tenant-scoped with a defense-in-depth cross-tenant assertion. AUTONOMOUS — read-only.
notices.readRequest body ResolutionsGetInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
resolutionId | string · uuid | required | — |
Responses
- 200Successful invocation
ResolutionsGetOutputField Type Required Description resolutionobjectrequired — responseOriginenum: "kreto" | "outside" | nullrequired — outsideResponderenum: "cpa_firm" | "employer" | "outside_counsel" | "other" | …required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/resolutions.listList all resolution draft versions for a notice (newest version first) for the caller's tenant. Use to surface a notice's proposed responses and their approval status. Tenant-scoped with a defense-in-depth cross-tenant assertion. AUTONOMOUS — read-only.
List all resolution draft versions for a notice (newest version first) for the caller's tenant. Use to surface a notice's proposed responses and their approval status. Tenant-scoped with a defense-in-depth cross-tenant assertion. AUTONOMOUS — read-only.
notices.readRequest body ResolutionsListInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
Responses
- 200Successful invocation
ResolutionsListOutputField Type Required Description itemsarray<object>required — totalintegerrequired — runobjectoptional — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/resolutions.getApprovalLetterGet the current (head) response letter for a notice, as the document the authorising party reads before approving it: the body, its version and status, and whether it is filable. Readable by the notice's own employer entity principal or a viewer with sensitive-data access; every other caller gets the same 404 as a notice that does not exist. Tenant-, entity- and live-scoped. AUTONOMOUS — read-only.
Get the current (head) response letter for a notice, as the document the authorising party reads before approving it: the body, its version and status, and whether it is filable. Readable by the notice's own employer entity principal or a viewer with sensitive-data access; every other caller gets the same 404 as a notice that does not exist. Tenant-, entity- and live-scoped. AUTONOMOUS — read-only.
notices.readRequest body ResolutionsGetApprovalLetterInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
Responses
- 200Successful invocation
ResolutionsGetApprovalLetterOutputField Type Required Description letterobjectrequired — crossReferenceFormulaGapbooleanrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/resolutions.reconcileAndDraftReconcile an actionable notice against the entity's connected payroll and draft a resolution. Returns needs_connection (no payroll linked) or needs_input (payroll missing for the period) synchronously; otherwise enqueues the reconcile+draft worker job and returns 'queued' (read the draft + line-level trace via resolutions.get/list). AUTONOMOUS — computes + queues; a human reviews the draft before anything files.
Reconcile an actionable notice against the entity's connected payroll and draft a resolution. Returns needs_connection (no payroll linked) or needs_input (payroll missing for the period) synchronously; otherwise enqueues the reconcile+draft worker job and returns 'queued' (read the draft + line-level trace via resolutions.get/list). AUTONOMOUS — computes + queues; a human reviews the draft before anything files.
notices.readRequest body ResolutionsReconcileAndDraftInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string · uuid | required | — |
Responses
- 200Successful invocation
ResolutionsReconcileAndDraftOutput - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 409Conflict. `FACTS_UNCONFIRMED`: the CPA has not confirmed what Kreto read (employer, notice type, deadline) — POST /api/notices/{id}/confirm-facts first; the same refusal POST /api/notices/{id}/draft answers. Asked after the employer refusal (`ENTITY_UNCONFIRMED` speaks first). Nothing is written and nothing is enqueued. Any other code: the notice cannot be drafted yet (the envelope names it).
one of (L2FactsUnconfirmedRefusal · L2ErrorEnvelope) - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/resolutions.supplyInputSupply the missing fields a draft is blocked on, clearing needs_input (AC-6). Records the values so the reconcile can re-run, and surfaces the draft for review. NOTIFY — writes a human-reviewed update; files nothing.
Supply the missing fields a draft is blocked on, clearing needs_input (AC-6). Records the values so the reconcile can re-run, and surfaces the draft for review. NOTIFY — writes a human-reviewed update; files nothing.
notices.writeRequest body ResolutionsSupplyInputInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
resolutionId | string · uuid | required | — |
inputs | object | required | — |
Responses
- 200Successful invocation
ResolutionsSupplyInputOutputField Type Required Description resolutionobjectrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/resolutions.recordOutcomeRecord an agency response on a resolution — acceptance confirms the matter, anything else reopens it (AC-12) — and seal the transition into the SHA-256 audit chain. AUTONOMOUS — deterministic classification of the agency's reply; no human gate.
Record an agency response on a resolution — acceptance confirms the matter, anything else reopens it (AC-12) — and seal the transition into the SHA-256 audit chain. AUTONOMOUS — deterministic classification of the agency's reply; no human gate.
notices.writeRequest body ResolutionsRecordOutcomeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
resolutionId | string · uuid | required | — |
agencyResponseType | enum: "acceptance" | "counter" | "additional_info_request" | "rejection" | required | — |
Responses
- 200Successful invocation
ResolutionsRecordOutcomeOutputField Type Required Description resolutionobjectrequired — outcomeenum: "confirmed" | "reopened"required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/resolutions.approveApprove a resolution draft (transitions draft -> approved). Requires a typed confirmation phrase ("APPROVE"). APPROVAL_REQUIRED — human gate; files/moves nothing by itself (execute is the external-impact step). UI-only: not exposed via MCP/CLI.
Approve a resolution draft (transitions draft -> approved). Requires a typed confirmation phrase ("APPROVE"). APPROVAL_REQUIRED — human gate; files/moves nothing by itself (execute is the external-impact step). UI-only: not exposed via MCP/CLI.
notices.writeRequest body ResolutionsApproveInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
resolutionId | string · uuid | required | — |
confirmationPhrase | string | required | Typed confirmation — must equal "APPROVE" (AC-9 typed-confirm). |
payrollOverrideReason | string | optional | Written justification for approving without a completed payroll cross-reference. Recorded in the hash-chained audit trail. |
Responses
- 200Successful invocation
ResolutionsApproveOutputField Type Required Description resolutionobjectrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 409Conflict. `draft_superseded`: the draft id was replaced by a newer version (one live draft per notice) — nothing is written; approve `live_draft_id` instead. Any other code: the draft or notice is not in an approvable state (the L2 envelope names it).
one of (L2DraftSupersededRefusal · L2LetterRefusalEnvelope) - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/resolutions.rejectReject a resolution draft (transitions draft -> rejected). Records the reason; redraft=true signals the redraft loop, otherwise the matter is finalized. APPROVAL_REQUIRED — human gate; UI-only, not MCP/CLI-exposed.
Reject a resolution draft (transitions draft -> rejected). Records the reason; redraft=true signals the redraft loop, otherwise the matter is finalized. APPROVAL_REQUIRED — human gate; UI-only, not MCP/CLI-exposed.
notices.writeRequest body ResolutionsRejectInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
resolutionId | string · uuid | required | — |
reason | string | optional | — |
redraft | boolean | optional | — |
Responses
- 200Successful invocation
ResolutionsRejectOutputField Type Required Description resolutionobjectrequired — redraftJobobjectoptional — redraftQueueErrorenum: "requeue_failed"optional — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/resolutions.executeExecute an approved resolution: record the agency filing (operator-attested) and advance the notice to ACTIONED, sealing a hash-chained audit receipt. Requires the draft to be 'approved' (post-approval invariant) and not already executed. APPROVAL_REQUIRED — UI/session-only (not MCP/CLI); Kreto records the filing, it does not auto-transmit.
Execute an approved resolution: record the agency filing (operator-attested) and advance the notice to ACTIONED, sealing a hash-chained audit receipt. Requires the draft to be 'approved' (post-approval invariant) and not already executed. APPROVAL_REQUIRED — UI/session-only (not MCP/CLI); Kreto records the filing, it does not auto-transmit.
notices.writeRequest body ResolutionsExecuteInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
resolutionId | string · uuid | required | — |
filingMethod | enum: "mail" | "fax" | "online_portal" | "email" | … | required | — |
note | string | optional | — |
confirmationNumber | string | required | — |
Responses
- 200Successful invocation
ResolutionsExecuteOutputField Type Required Description resolutionobjectrequired — filingobjectoptional — sealedAtstringoptional — employerReportobjectoptional — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 409Conflict. `DRAFT_NOT_FILABLE`: the head letter is not a filable document — `unfilable_reason` names why (`unfilled_fields` / `unfilled_field_count` for unfilled slots). Any other code: the notice or letter is not in a fileable state (the envelope names it).
L2LetterRefusalEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/resolutions.recordOutsideResponseRecord that the agency was answered OUTSIDE Kreto (by the CPA firm, the employer or outside counsel): moves the notice to filed with response_origin='outside', stamps the attested date, party and channel, retires any live Kreto draft, and seals one audit row. APPROVAL_REQUIRED — a human attestation that an obligation was met; UI/session-only.
Record that the agency was answered OUTSIDE Kreto (by the CPA firm, the employer or outside counsel): moves the notice to filed with response_origin='outside', stamps the attested date, party and channel, retires any live Kreto draft, and seals one audit row. APPROVAL_REQUIRED — a human attestation that an obligation was met; UI/session-only.
notices.writeRequest body ResolutionsRecordOutsideResponseInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string | required | — |
responder | enum: "cpa_firm" | "employer" | "outside_counsel" | "other" | required | — |
channel | enum: "mail" | "fax" | "online_portal" | "email" | … | required | — |
respondedOn | string | required | — |
documentId | string | optional | — |
confirmationNumber | string | optional | — |
note | string | optional | — |
Responses
- 200Successful invocation
ResolutionsRecordOutsideResponseOutputField Type Required Description noticeIdstringrequired — lifecycleStateenum: "filed"required — responseOriginenum: "outside"required — outsideResponderenum: "cpa_firm" | "employer" | "outside_counsel" | "other"required — filedAtstringrequired — retiredDraftIdstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/resolutions.withdrawApprovalWithdraw the approval of a notice's letter that has NOT been sent or filed: the notice returns to drafting, the approved letter of record is kept as its version marked 'withdrawn' (the next draft is the next version), and one hash-chained audit row records who, when and the required reason — all in one transaction. Refused (409) once the letter is filed, reported filed, answered outside Kreto, sent for signature, or the notice is closed; 404 for an unknown or foreign notice. APPROVAL_REQUIRED — a human act; interactive CPA-firm sessions only.
Withdraw the approval of a notice's letter that has NOT been sent or filed: the notice returns to drafting, the approved letter of record is kept as its version marked 'withdrawn' (the next draft is the next version), and one hash-chained audit row records who, when and the required reason — all in one transaction. Refused (409) once the letter is filed, reported filed, answered outside Kreto, sent for signature, or the notice is closed; 404 for an unknown or foreign notice. APPROVAL_REQUIRED — a human act; interactive CPA-firm sessions only.
notices.writeRequest body ResolutionsWithdrawApprovalInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
noticeId | string | required | — |
reason | string | required | — |
Responses
- 200Successful invocation
ResolutionsWithdrawApprovalOutputField Type Required Description noticeIdstringrequired — lifecycleStatestringrequired — withdrawnDraftIdstringrequired — withdrawnDraftVersionintegerrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:signature-consents5
POST/v1/signature-consents.captureConsentPersist an employer officer's signature + scoped consent for state filings. Use when an officer has signed on a canvas and explicitly authorized Kreto to apply the captured signature to per-state PDFs (Middesk-match signature-warehouse model). Idempotent: returns existing un-revoked consent for the same (officer, scope) instead of duplicating. Returns only the consent's id, scope and signed_at — never the signature image, IP, user agent or consent text.
Persist an employer officer's signature + scoped consent for state filings. Use when an officer has signed on a canvas and explicitly authorized Kreto to apply the captured signature to per-state PDFs (Middesk-match signature-warehouse model). Idempotent: returns existing un-revoked consent for the same (officer, scope) instead of duplicating. Returns only the consent's id, scope and signed_at — never the signature image, IP, user agent or consent text.
state_registration.writeRequest body SignatureConsentsCaptureConsentInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
officerMemberId | string | required | — |
scope | enum: "sos_and_payroll_tax" | "state_registration_only" | "all_state_filings" | required | — |
signatureDataUrl | string | required | — |
ip | string | required | — |
userAgent | string | required | — |
Responses
- 200Successful invocation
SignatureConsentsCaptureConsentOutputField Type Required Description consentobjectrequired — isNewCapturebooleanrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/signature-consents.getActiveConsentCheck whether an entity has an active (un-revoked) signature consent for a scope, optionally for one officer. Returns only the consent's id, scope and signed_at — never the signature image, IP, user agent or consent text. Returns null if no active consent exists — caller should prompt the officer to sign first.
Check whether an entity has an active (un-revoked) signature consent for a scope, optionally for one officer. Returns only the consent's id, scope and signed_at — never the signature image, IP, user agent or consent text. Returns null if no active consent exists — caller should prompt the officer to sign first.
state_registration.readRequest body SignatureConsentsGetActiveConsentInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
scope | enum: "sos_and_payroll_tax" | "state_registration_only" | "all_state_filings" | required | — |
officerMemberId | string | optional | — |
Responses
- 200Successful invocation
SignatureConsentsGetActiveConsentOutputField Type Required Description consentobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/signature-consents.revokeConsentSoft-revoke a signature consent so it cannot be applied to future filings. Past filings already stamped with the signature retain their stamps (audit immutability). Use when an officer leaves the company or the customer wants to withdraw authorization. Returns only the consent's id, scope, signed_at and revoked_at.
Soft-revoke a signature consent so it cannot be applied to future filings. Past filings already stamped with the signature retain their stamps (audit immutability). Use when an officer leaves the company or the customer wants to withdraw authorization. Returns only the consent's id, scope, signed_at and revoked_at.
state_registration.writeRequest body SignatureConsentsRevokeConsentInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
consentId | string | required | — |
revocationReason | string | required | — |
Responses
- 200Successful invocation
SignatureConsentsRevokeConsentOutputField Type Required Description consentobjectrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/signature-consents.attestSignatoryAuthorityThe employer's officer confirms, from their own login, that they are an owner/partner/member/corporate officer with authority to bind the entity — setting entity_members.is_business_signatory on their OWN row. Refuses for any other user's row. This is the only writer of that flag; a CPA cannot attest on an officer's behalf (use requestSignatoryAttestation to ask).
The employer's officer confirms, from their own login, that they are an owner/partner/member/corporate officer with authority to bind the entity — setting entity_members.is_business_signatory on their OWN row. Refuses for any other user's row. This is the only writer of that flag; a CPA cannot attest on an officer's behalf (use requestSignatoryAttestation to ask).
state_registration.writeRequest body SignatureConsentsAttestSignatoryAuthorityInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
officerMemberId | string | required | — |
title | string | required | — |
attestationAccepted | enum: true | required | — |
attestationTextShown | string | optional | — |
ip | string | required | — |
userAgent | string | required | — |
Responses
- 200Successful invocation
SignatureConsentsAttestSignatoryAuthorityOutputField Type Required Description officerMemberIdstringrequired — isBusinessSignatoryenum: truerequired — authorityBasisenum: "officer_self"required — attestationTextstringrequired — attestationTextHashstringrequired — titlestringrequired — attestedAtstringrequired — alreadyAttestedbooleanrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/signature-consents.requestSignatoryAttestationAsk an employer officer to confirm their authority to sign (the CPA nomination) — or, when they already are a signatory, to sign the registration. Emails the officer a link to their own login. NEVER sets entity_members.is_business_signatory — the nomination stays pending until the officer attests themselves.
Ask an employer officer to confirm their authority to sign (the CPA nomination) — or, when they already are a signatory, to sign the registration. Emails the officer a link to their own login. NEVER sets entity_members.is_business_signatory — the nomination stays pending until the officer attests themselves.
state_registration.writeRequest body SignatureConsentsRequestSignatoryAttestationInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
officerMemberId | string | required | — |
note | string | optional | — |
stateCode | string | optional | — |
Responses
- 200Successful invocation
SignatureConsentsRequestSignatoryAttestationOutputField Type Required Description officerMemberIdstringrequired — isBusinessSignatorybooleanrequired — requestedenum: truerequired — emailSentbooleanrequired — noContactOnFilebooleanrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:state-coverage1
POST/v1/state-coverage.getByStateFetch one US state's compliance coverage for an employer: all registrations (with state account numbers), open notices (not in terminal status), and open recommendations. Use when an agent or the UI drawer needs deep detail on a single state. Example agent question: 'What's our SUI registration status in California?' Returns empty arrays for any signal that has no data — never throws on missing rows.
Fetch one US state's compliance coverage for an employer: all registrations (with state account numbers), open notices (not in terminal status), and open recommendations. Use when an agent or the UI drawer needs deep detail on a single state. Example agent question: 'What's our SUI registration status in California?' Returns empty arrays for any signal that has no data — never throws on missing rows.
state_registration.readRequest body StateCoverageGetByStateInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
employerEntityId | string | required | — |
stateCode | string | required | — |
Responses
- 200Successful invocation
StateCoverageGetByStateOutputField Type Required Description stateCodestringrequired — registrationsarray<object>required — openNoticesarray<object>required — recommendationsarray<object>required — - 400Validation error (input schema rejected). risk_level=elevated.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:state-kb1
POST/v1/state-kb.getStateDetailOne US state's full compliance ruleset from Kreto's public reference catalog: the state agency and its portal, SUI tax rules for a given tax year, rate schedules, surcharges, notice types, protest rules, POA rules, statutes and the data-quality record. Global reference data — identical for every tenant, and no employer data is read. Use to ground reasoning about a notice before recommending an action. Example: 'what is Ohio's SUI protest deadline and which form files it?'
One US state's full compliance ruleset from Kreto's public reference catalog: the state agency and its portal, SUI tax rules for a given tax year, rate schedules, surcharges, notice types, protest rules, POA rules, statutes and the data-quality record. Global reference data — identical for every tenant, and no employer data is read. Use to ground reasoning about a notice before recommending an action. Example: 'what is Ohio's SUI protest deadline and which form files it?'
notices.readRequest body StateKbGetStateDetailInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
taxYear | integer | optional | — |
Responses
- 200Successful invocation
StateKbGetStateDetailOutputField Type Required Description state_codestringrequired — state_namestringrequired — agency_namestringrequired — agency_abbreviationstringrequired — agency_urlstringrequired — employer_portal_urlstringrequired — phonestringrequired — state_tax_rulesarray<object>required — state_rate_schedulesarray<object>required — state_surchargesarray<object>required — state_notice_typesarray<object>required — state_protest_rulesarray<object>required — state_poa_rulesobjectrequired — state_statutesarray<object>required — state_data_qualityobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `notices.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:state-registration26
POST/v1/state-registration.detectNewStateRequirementGiven a StateChangeEvent (an employee moved from one state to another), return the requirements that apply for the destination state. Reads the TS rules registry; does NOT write to DB. AUTONOMOUS — agents may call freely; the trigger pipeline lives in PR-D's worker handler.
Given a StateChangeEvent (an employee moved from one state to another), return the requirements that apply for the destination state. Reads the TS rules registry; does NOT write to DB. AUTONOMOUS — agents may call freely; the trigger pipeline lives in PR-D's worker handler.
state_registration.readRequest body StateRegistrationDetectNewStateRequirementInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateChangeEvent | object | required | — |
Responses
- 200Successful invocation
StateRegistrationDetectNewStateRequirementOutputField Type Required Description requirementsarray<object>required — reasoningstringrequired — stateSupportedbooleanrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.listRequiredAccountsList required state-agency accounts for a given state. When employerEntityId is supplied, also returns the subset of requirements already registered (status NOT IN rejected/expired) for that employer. Reads from rules registry. AUTONOMOUS.
List required state-agency accounts for a given state. When employerEntityId is supplied, also returns the subset of requirements already registered (status NOT IN rejected/expired) for that employer. Reads from rules registry. AUTONOMOUS.
state_registration.readRequest body StateRegistrationListRequiredAccountsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
employerEntityId | string | optional | — |
Responses
- 200Successful invocation
StateRegistrationListRequiredAccountsOutputField Type Required Description stateCodestringrequired — stateSupportedbooleanrequired — accountsarray<object>required — alreadyRegisteredarray<enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | …>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.prepareRegistrationFormValidate a registration form payload against the per-state Zod schema and persist (upsert) a row in employer_state_registrations with status='prepared'. dryRun=true returns the validated payload without persisting. APPROVAL_REQUIRED — Phase 0 metadata only (HITL gate in Phase 3).
Validate a registration form payload against the per-state Zod schema and persist (upsert) a row in employer_state_registrations with status='prepared'. dryRun=true returns the validated payload without persisting. APPROVAL_REQUIRED — Phase 0 metadata only (HITL gate in Phase 3).
state_registration.writeRequest body StateRegistrationPrepareRegistrationFormInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
stateCode | string | required | — |
requirementType | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | required | — |
formPayload | object | optional | — |
dryRun | boolean | optional | — |
source | enum: "manual" | "mosey" | "portal" | optional | — |
sourceExternalId | string | optional | — |
Responses
- 200Successful invocation
StateRegistrationPrepareRegistrationFormOutputField Type Required Description registrationobjectrequired — validatedPayloadobjectoptional — dryRunbooleanrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.submitRegistrationSubmit a prepared registration to the state portal via Browserbase. The most consequential method in Phase E — actually files with a government agency. APPROVAL_REQUIRED (Phase 0 metadata-only; HITL gate in Phase 3). Dispatches via the StatePortalAdapter registry; throws NotImplementedError for states without a wired adapter.
Submit a prepared registration to the state portal via Browserbase. The most consequential method in Phase E — actually files with a government agency. APPROVAL_REQUIRED (Phase 0 metadata-only; HITL gate in Phase 3). Dispatches via the StatePortalAdapter registry; throws NotImplementedError for states without a wired adapter.
state_registration.writeRequest body StateRegistrationSubmitRegistrationInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
registrationId | string | required | — |
dryRun | boolean | optional | — |
Responses
- 200Successful invocation
StateRegistrationSubmitRegistrationOutputField Type Required Description registrationobjectrequired — dryRunbooleanrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.checkRegistrationStatusReturn the current status of a registration. PR-A reads DB last-known state only (pollSource='db_only'). PR-B/C wires actual portal polling via Browserbase (pollSource='portal_polled'). AUTONOMOUS.
Return the current status of a registration. PR-A reads DB last-known state only (pollSource='db_only'). PR-B/C wires actual portal polling via Browserbase (pollSource='portal_polled'). AUTONOMOUS.
state_registration.readRequest body StateRegistrationCheckRegistrationStatusInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
registrationId | string | required | — |
Responses
- 200Successful invocation
StateRegistrationCheckRegistrationStatusOutputField Type Required Description registrationobjectrequired — pollSourceenum: "db_only" | "portal_polled"required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.getRegistrationByEntityLook up the in-flight registration for (tenantId, entityId, stateCode, requirementType). Returns null when no row matches (rejected/expired excluded). AUTONOMOUS.
Look up the in-flight registration for (tenantId, entityId, stateCode, requirementType). Returns null when no row matches (rejected/expired excluded). AUTONOMOUS.
state_registration.readRequest body StateRegistrationGetRegistrationByEntityInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
stateCode | string | required | — |
requirementType | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | required | — |
Responses
- 200Successful invocation
StateRegistrationGetRegistrationByEntityOutputField Type Required Description registrationobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.listRegistrationsPaginated read of employer_state_registrations rows for the tracking UI. Tenant-scoped via ctx.tenantId; optional entity / status / state / requirement_type / source filters. Returns at most pageSize rows per call (default 25, max 100).
Paginated read of employer_state_registrations rows for the tracking UI. Tenant-scoped via ctx.tenantId; optional entity / status / state / requirement_type / source filters. Returns at most pageSize rows per call (default 25, max 100).
state_registration.readRequest body StateRegistrationListRegistrationsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | optional | — |
status | enum: "pending" | "prepared" | "awaiting_approval" | "preflight_failed" | … | optional | — |
stateCode | string | optional | — |
requirementType | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | optional | — |
source | enum: "manual" | "mosey" | "portal" | optional | — |
page | integer | optional | — |
pageSize | integer | optional | — |
Responses
- 200Successful invocation
StateRegistrationListRegistrationsOutputField Type Required Description registrationsarray<object>required — pageintegerrequired — pageSizeintegerrequired — totalintegerrequired — hasMorebooleanrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.transitionStatusApply a manual CPA-driven status transition to an existing registration row. Validates the (from, to) pair against the per-state allowedTransitions matrix (with universal default fallback). Writes a structured audit event capturing the transition + reason. NOTIFY classification: agents can call but the action is logged for human review.
Apply a manual CPA-driven status transition to an existing registration row. Validates the (from, to) pair against the per-state allowedTransitions matrix (with universal default fallback). Writes a structured audit event capturing the transition + reason. NOTIFY classification: agents can call but the action is logged for human review.
state_registration.writeRequest body StateRegistrationTransitionStatusInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
registrationId | string | required | — |
newStatus | enum: "pending" | "prepared" | "awaiting_approval" | "preflight_failed" | … | required | — |
reason | string | required | — |
stateAccountNumber | string | optional | — |
filedAt | string · date-time | optional | — |
Responses
- 200Successful invocation
StateRegistrationTransitionStatusOutputField Type Required Description registrationobjectrequired — fromStatusenum: "pending" | "prepared" | "awaiting_approval" | "preflight_failed" | …required — toStatusenum: "pending" | "prepared" | "awaiting_approval" | "preflight_failed" | …required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.updateRegistrationMetadataManually edit the tracker metadata fields (assigned_rate as a decimal fraction, assigned_deposit_frequency, effective_date, next_renewal_at) on an existing registration row. Absent key = unchanged, null = clear. Appends a human-readable notes line and a hash-chained audit_events row carrying per-field old → new + the required reason. `source` is never changed (creation provenance). NOTIFY classification: agents can call but the action is logged for human review. On a sui_registration row, a rate or effective-date edit (or a stated rateYear) also records the SUI rate of record for that year, atomically with the edit.
Manually edit the tracker metadata fields (assigned_rate as a decimal fraction, assigned_deposit_frequency, effective_date, next_renewal_at) on an existing registration row. Absent key = unchanged, null = clear. Appends a human-readable notes line and a hash-chained audit_events row carrying per-field old → new + the required reason. `source` is never changed (creation provenance). NOTIFY classification: agents can call but the action is logged for human review. On a sui_registration row, a rate or effective-date edit (or a stated rateYear) also records the SUI rate of record for that year, atomically with the edit.
state_registration.writeRequest body StateRegistrationUpdateRegistrationMetadataInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
registrationId | string | required | — |
assignedRate | number | optional | — |
assignedDepositFrequency | enum: "weekly" | "biweekly" | "semimonthly" | "monthly" | … | optional | — |
effectiveDate | string | optional | — |
nextRenewalAt | string | optional | — |
rateYear | integer | optional | — |
reason | string | required | — |
Responses
- 200Successful invocation
StateRegistrationUpdateRegistrationMetadataOutputField Type Required Description registrationobjectrequired — changedFieldsarray<string>required — rateOfRecordobjectoptional — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.recordPortalResultInbound write path for state portal scrapers. Transitions a registration row to approved or rejected based on the portal's outcome, sets state_account_number on success, captures last_error on failure, and emits a recommendation row that the alert cron uses to email/push the CPA team. APPROVAL_REQUIRED per /goal Q8 decision — customer + external-facing writes are NOT MCP-exposable; the audit row carries the elevated risk level so Phase 3 HITL gates these calls. Auth: server-to-server via API key with scope state_registration.write.
Inbound write path for state portal scrapers. Transitions a registration row to approved or rejected based on the portal's outcome, sets state_account_number on success, captures last_error on failure, and emits a recommendation row that the alert cron uses to email/push the CPA team. APPROVAL_REQUIRED per /goal Q8 decision — customer + external-facing writes are NOT MCP-exposable; the audit row carries the elevated risk level so Phase 3 HITL gates these calls. Auth: server-to-server via API key with scope state_registration.write.
state_registration.writeRequest body StateRegistrationRecordPortalResultInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
registrationId | string | required | — |
outcome | enum: "approved" | "rejected" | required | — |
stateAccountNumber | string | optional | — |
decidedAt | string · date-time | optional | — |
lastError | string | optional | — |
Responses
- 200Successful invocation
StateRegistrationRecordPortalResultOutputField Type Required Description registrationobjectrequired — recommendationIdstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.generateDe1FilingGenerate a draft California Form DE 1 (covers SUI + Withholding + SDI/PFL on a single CA EDD form) filled with the employer's intake data and stamped with the captured officer signature. Creates a state_filings parent row + 3 employer_state_registrations rows (SUI/Withholding/PFML) all linked via filing_id. NOTIFY risk: this is a draft for human review before CP-CA3 submits to myEDD.
Generate a draft California Form DE 1 (covers SUI + Withholding + SDI/PFL on a single CA EDD form) filled with the employer's intake data and stamped with the captured officer signature. Creates a state_filings parent row + 3 employer_state_registrations rows (SUI/Withholding/PFML) all linked via filing_id. NOTIFY risk: this is a draft for human review before CP-CA3 submits to myEDD.
state_registration.writeRequest body StateRegistrationGenerateDe1FilingInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
employerEntityId | string | required | — |
recommendationId | string | optional | — |
signatureConsentId | string | required | — |
intake | object | required | — |
Responses
- 200Successful invocation
StateRegistrationGenerateDe1FilingOutputField Type Required Description filingIdstringrequired — filingPdfUrlstringrequired — employerStateRegistrationIdsarray<string>required — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.generateStateFilingState-agnostic filing generator. Resolves the per-state rule for the given stateCode, validates intake against rule.stateIntakeSchema, embeds the captured signature, generates a draft PDF (from rule.pdfTemplatePath if set, else programmatic), and creates a state_filings parent row + one employer_state_registrations row per covered requirement_type. NOTIFY risk: draft only — submission to the state portal is CP-CA3 (APPROVAL_REQUIRED). Replaces the CA-specific generateDe1Filing for all new states.
State-agnostic filing generator. Resolves the per-state rule for the given stateCode, validates intake against rule.stateIntakeSchema, embeds the captured signature, generates a draft PDF (from rule.pdfTemplatePath if set, else programmatic), and creates a state_filings parent row + one employer_state_registrations row per covered requirement_type. NOTIFY risk: draft only — submission to the state portal is CP-CA3 (APPROVAL_REQUIRED). Replaces the CA-specific generateDe1Filing for all new states.
state_registration.writeRequest body StateRegistrationGenerateStateFilingInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
employerEntityId | string | required | — |
recommendationId | string | optional | — |
signatureConsentId | string | required | — |
intake | object | required | — |
Responses
- 200Successful invocation
StateRegistrationGenerateStateFilingOutputField Type Required Description filingIdstringrequired — filingPdfUrlstringrequired — employerStateRegistrationIdsarray<string>required — formobjectrequired — recommendationobjectoptional — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.saveIntakeDraftUpsert the in-progress State Registration Wizard intake for a given (employer, state) so wizard state survives a refresh / crash / cross-device resume. Draft-only (NOTIFY): captures answers + resume metadata, generates no filing and submits nothing. One row per (tenant, employer, state) via the table's UNIQUE constraint — re-saving updates, never duplicates.
Upsert the in-progress State Registration Wizard intake for a given (employer, state) so wizard state survives a refresh / crash / cross-device resume. Draft-only (NOTIFY): captures answers + resume metadata, generates no filing and submits nothing. One row per (tenant, employer, state) via the table's UNIQUE constraint — re-saving updates, never duplicates.
state_registration.writeRequest body StateRegistrationSaveIntakeDraftInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
employerEntityId | string | required | — |
recommendationId | string | optional | — |
stateCode | string | required | — |
reasonForRegistering | string | optional | — |
qualifiers | object | optional | — |
stateSpecificData | object | required | — |
Responses
- 200Successful invocation
StateRegistrationSaveIntakeDraftOutputField Type Required Description draftIdstringrequired — updatedAtstringrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.provisionPortalAccountAllocate a Kreto-owned portal-account credential for a (tenant, entity, state, agency) tuple. Generates a unique alias e-mail of the form <slug>.portal-<state>-<agency>@kreto.ai, generates a strong base64url password, and writes the credential to vault under agency_id='portal_<stateCode>'. Idempotent: re-call with the same tuple returns the existing credential. APPROVAL_REQUIRED (Phase 0 metadata-only; KRT-1554 step-up gates later).
Allocate a Kreto-owned portal-account credential for a (tenant, entity, state, agency) tuple. Generates a unique alias e-mail of the form <slug>.portal-<state>-<agency>@kreto.ai, generates a strong base64url password, and writes the credential to vault under agency_id='portal_<stateCode>'. Idempotent: re-call with the same tuple returns the existing credential. APPROVAL_REQUIRED (Phase 0 metadata-only; KRT-1554 step-up gates later).
state_registration.writeRequest body StateRegistrationProvisionPortalAccountInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | required | — |
stateCode | string | required | — |
agencyCode | string | required | — |
Responses
- 200Successful invocation
StateRegistrationProvisionPortalAccountOutputField Type Required Description credentialIdstringrequired — aliasEmailstring · emailrequired — agencyIdstringrequired — statusstringrequired — passwordstringrequired — newlyProvisionedbooleanrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.listCatalogRulesBrowse the state-registration catalog (state_filing_rules, global config — NO tenant filter). Optional filters: stateCode, requirementType, verificationStatus, needsResearch, monopolistic, jurisdictionType. Returns the matching rows plus HONEST per-status counts ({unverified, human_reviewed, verified}) for the filtered set — never a 'done/complete' claim (AC-23). AUTONOMOUS, read-only.
Browse the state-registration catalog (state_filing_rules, global config — NO tenant filter). Optional filters: stateCode, requirementType, verificationStatus, needsResearch, monopolistic, jurisdictionType. Returns the matching rows plus HONEST per-status counts ({unverified, human_reviewed, verified}) for the filtered set — never a 'done/complete' claim (AC-23). AUTONOMOUS, read-only.
state_registration.readRequest body StateRegistrationListCatalogRulesInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | optional | — |
requirementType | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | optional | — |
verificationStatus | enum: "unverified" | "human_reviewed" | "verified" | optional | — |
needsResearch | boolean | optional | — |
monopolistic | boolean | optional | — |
jurisdictionType | enum: "federal" | "state" | "locality" | optional | — |
Responses
- 200Successful invocation
StateRegistrationListCatalogRulesOutputField Type Required Description rowsarray<object>required — countsobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.promoteCatalogRuleHuman gate: promote a catalog cell along the verification ladder (unverified → human_reviewed → verified). APPROVAL_REQUIRED, UI-only — excluded from MCP/CLI, and the handler REJECTS any non-human principal before any write (AC-20). Verifying requires provenance (source_url + retrieved_at, AC-18). Stamps reviewed_by / last_reviewed_at, and on verify also verified_at / verified_by / needs_research=false.
Human gate: promote a catalog cell along the verification ladder (unverified → human_reviewed → verified). APPROVAL_REQUIRED, UI-only — excluded from MCP/CLI, and the handler REJECTS any non-human principal before any write (AC-20). Verifying requires provenance (source_url + retrieved_at, AC-18). Stamps reviewed_by / last_reviewed_at, and on verify also verified_at / verified_by / needs_research=false.
state_registration.writeRequest body StateRegistrationPromoteCatalogRuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
requirementType | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | required | — |
toStatus | enum: "human_reviewed" | "verified" | required | — |
reason | string | optional | — |
Responses
- 200Successful invocation
StateRegistrationPromoteCatalogRuleOutputField Type Required Description stateCodestringrequired — requirementTypeenum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | …required — fromStatusenum: "unverified" | "human_reviewed" | "verified"required — toStatusenum: "unverified" | "human_reviewed" | "verified"required — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden. `SYSTEM_ADMIN_REQUIRED`: the caller is not a Kreto system administrator on an interactive session — the global catalog every firm pins (handbook modules, state filing / foreign-qualification / annual-report / e-delivery rules) is staff-only, exactly as on the REST admin door. Checked before any catalog read, so the answer is the same whether or not the row exists. `STEP_UP_REQUIRED`: the dispatcher's APPROVAL_REQUIRED gate (an API key, or no fresh re-authentication). Nothing is written.
one of (L2SystemAdminRequiredRefusal · L2ErrorEnvelope) - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.getCatalogRuleRead one catalog cell (state_filing_rules, global config — NO tenant filter): the rule row plus its typed ordered steps (state_filing_rule_steps), its prerequisite edges (state_filing_rule_prerequisites), and the resolved jurisdiction (via jurisdiction_code, else null). Powers the Cell Detail screen (jobs a+b, AC-5). AUTONOMOUS, read-only.
Read one catalog cell (state_filing_rules, global config — NO tenant filter): the rule row plus its typed ordered steps (state_filing_rule_steps), its prerequisite edges (state_filing_rule_prerequisites), and the resolved jurisdiction (via jurisdiction_code, else null). Powers the Cell Detail screen (jobs a+b, AC-5). AUTONOMOUS, read-only.
state_registration.readRequest body StateRegistrationGetCatalogRuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
requirementType | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | required | — |
Responses
- 200Successful invocation
StateRegistrationGetCatalogRuleOutputField Type Required Description ruleobjectrequired — stepsarray<object>required — prerequisitesarray<object>required — jurisdictionobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.listFilingRuleStepsList the typed, ordered registration steps for a (state, requirement) cell (state_filing_rule_steps, global config — NO tenant filter), ordered by step_order. The canonical step sequence is typed rows, never a prose blob (AC-7). AUTONOMOUS, read-only.
List the typed, ordered registration steps for a (state, requirement) cell (state_filing_rule_steps, global config — NO tenant filter), ordered by step_order. The canonical step sequence is typed rows, never a prose blob (AC-7). AUTONOMOUS, read-only.
state_registration.readRequest body StateRegistrationListFilingRuleStepsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
requirementType | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | required | — |
Responses
- 200Successful invocation
StateRegistrationListFilingRuleStepsOutputField Type Required Description stepsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.listFilingRulePrerequisitesList the prerequisite dependency edges for a (state, requirement) cell (state_filing_rule_prerequisites, global config — NO tenant filter). Edges model account-type dependencies and SOS foreign qualification (prerequisite_kind='sos_foreign_qual', an edge — NOT a 6th requirement_type), queryable as a dependency chain (AC-10/13). AUTONOMOUS, read-only.
List the prerequisite dependency edges for a (state, requirement) cell (state_filing_rule_prerequisites, global config — NO tenant filter). Edges model account-type dependencies and SOS foreign qualification (prerequisite_kind='sos_foreign_qual', an edge — NOT a 6th requirement_type), queryable as a dependency chain (AC-10/13). AUTONOMOUS, read-only.
state_registration.readRequest body StateRegistrationListFilingRulePrerequisitesInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
requirementType | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | required | — |
Responses
- 200Successful invocation
StateRegistrationListFilingRulePrerequisitesOutputField Type Required Description prerequisitesarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.listJurisdictionsList jurisdictions (federal | state | locality) from the jurisdictions table (global config — NO tenant filter). Optional filters: jurisdictionType, parentCode. Locality-ready model — v1 seeds state-grain only, but a locality is just a future row (AC-15/16). AUTONOMOUS, read-only.
List jurisdictions (federal | state | locality) from the jurisdictions table (global config — NO tenant filter). Optional filters: jurisdictionType, parentCode. Locality-ready model — v1 seeds state-grain only, but a locality is just a future row (AC-15/16). AUTONOMOUS, read-only.
state_registration.readRequest body StateRegistrationListJurisdictionsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
jurisdictionType | enum: "federal" | "state" | "locality" | optional | — |
parentCode | string | optional | — |
Responses
- 200Successful invocation
StateRegistrationListJurisdictionsOutputField Type Required Description jurisdictionsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.proposeFilingRuleResearch one (state, requirement) catalog cell via the Claude + web_search agent and upsert an UNVERIFIED proposal onto the cell BY PK (state_filing_rules, global config — NO tenant filter). The write ALWAYS leaves verification_status='unverified' and NEVER sets verified/human_reviewed (AC-19/20 — agents may never verify); writes provenance (source_url + retrieved_at, AC-18); idempotent-by-PK (AC-22). MUST NOT downgrade a human-promoted cell: on a human_reviewed OR verified cell it leaves the authoritative fields untouched and returns the existing row + the fresh proposal so a human can compare (no-downgrade). PII-free. NOTIFY: agents may call; a human reviews the proposal before promotion.
Research one (state, requirement) catalog cell via the Claude + web_search agent and upsert an UNVERIFIED proposal onto the cell BY PK (state_filing_rules, global config — NO tenant filter). The write ALWAYS leaves verification_status='unverified' and NEVER sets verified/human_reviewed (AC-19/20 — agents may never verify); writes provenance (source_url + retrieved_at, AC-18); idempotent-by-PK (AC-22). MUST NOT downgrade a human-promoted cell: on a human_reviewed OR verified cell it leaves the authoritative fields untouched and returns the existing row + the fresh proposal so a human can compare (no-downgrade). PII-free. NOTIFY: agents may call; a human reviews the proposal before promotion.
state_registration.writeRequest body StateRegistrationProposeFilingRuleInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
stateCode | string | required | — |
requirementType | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | required | — |
Responses
- 200Successful invocation
StateRegistrationProposeFilingRuleOutputField Type Required Description rowobjectrequired — proposalobjectrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.approveRegistrationFilingDORMANT (fold-83): Kreto does not file state registrations yet, so every approval refuses STATE_FILING_NOT_AVAILABLE before any write or charge. When enabled: record a CPA's approval to file a prepared registration and, for fee-bearing filings, authorize payment up to a bound amount (AC-15). The consequential human gate — APPROVAL_REQUIRED, UI-only, never exposed to MCP/CLI. Moves the registration to 'awaiting_approval' and enqueues pre-flight. Handler: Phase B.
DORMANT (fold-83): Kreto does not file state registrations yet, so every approval refuses STATE_FILING_NOT_AVAILABLE before any write or charge. When enabled: record a CPA's approval to file a prepared registration and, for fee-bearing filings, authorize payment up to a bound amount (AC-15). The consequential human gate — APPROVAL_REQUIRED, UI-only, never exposed to MCP/CLI. Moves the registration to 'awaiting_approval' and enqueues pre-flight. Handler: Phase B.
state_registration.writeRequest body StateRegistrationApproveRegistrationFilingInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
registrationId | string | required | — |
authorizedAmountCents | integer | required | — |
billingDisclosure | string | optional | — |
Responses
- 200Successful invocation
StateRegistrationApproveRegistrationFilingOutputField Type Required Description registrationIdstringrequired — statusstringrequired — approvalIdstringrequired — preflightobjectrequired — enqueuedbooleanrequired — feeobjectrequired — usageobjectrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 409Not available (code `STATE_FILING_NOT_AVAILABLE`): Kreto does not file state registrations yet. Nothing is written, nothing is charged, nothing is enqueued and the row's status does not change.
L2StateFilingNotAvailableRefusal - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.runFilingPreflightRun the pre-flight filing validator (KRT-1544 rules: registration status, duplicate-account, idempotency, payload completeness) before any browser session launches (AC-2). Read-only, AUTONOMOUS. A failure blocks the filing and names the failing rule. Handler: Phase B.
Run the pre-flight filing validator (KRT-1544 rules: registration status, duplicate-account, idempotency, payload completeness) before any browser session launches (AC-2). Read-only, AUTONOMOUS. A failure blocks the filing and names the failing rule. Handler: Phase B.
state_registration.readRequest body StateRegistrationRunFilingPreflightInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
registrationId | string | required | — |
Responses
- 200Successful invocation
StateRegistrationRunFilingPreflightOutputField Type Required Description okbooleanrequired — failuresarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.verifyFilingOutcomeVerify an executor's claimed filing outcome against captured evidence (confirmation number + screenshot, optional re-check) before the registration is allowed to flip to 'submitted' (AC-5/AC-6). An unverifiable claim yields 'rejected' → needs_human, never 'submitted'. NOTIFY. Handler: Phase E.
Verify an executor's claimed filing outcome against captured evidence (confirmation number + screenshot, optional re-check) before the registration is allowed to flip to 'submitted' (AC-5/AC-6). An unverifiable claim yields 'rejected' → needs_human, never 'submitted'. NOTIFY. Handler: Phase E.
state_registration.writeRequest body StateRegistrationVerifyFilingOutcomeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
attemptId | string | required | — |
Responses
- 200Successful invocation
StateRegistrationVerifyFilingOutcomeOutputField Type Required Description verdictenum: "confirmed" | "rejected"required — confirmationNumberstringrequired — reasonstringrequired — - 400Validation error (input schema rejected). risk_level=notify.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.storeIssuedCredentialStore a portal credential created during registration into the per-tenant credential vault (AC-11). APPROVAL_REQUIRED — creating a credential is a consequential vault write, so it stays UI-only and is NOT exposed to MCP/CLI (verifier-gate finding: a NOTIFY op would be auto-exposed, contra the approved roster). The secret is captured by the vault layer, never passed in model context. Handler: Phase F (needs a real portal-issued credential).
Store a portal credential created during registration into the per-tenant credential vault (AC-11). APPROVAL_REQUIRED — creating a credential is a consequential vault write, so it stays UI-only and is NOT exposed to MCP/CLI (verifier-gate finding: a NOTIFY op would be auto-exposed, contra the approved roster). The secret is captured by the vault layer, never passed in model context. Handler: Phase F (needs a real portal-issued credential).
state_registration.writeRequest body StateRegistrationStoreIssuedCredentialInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
registrationId | string | required | — |
portalUsername | string | required | — |
agencyId | string | required | — |
Responses
- 200Successful invocation
StateRegistrationStoreIssuedCredentialOutputField Type Required Description credentialIdstringrequired — storedbooleanrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/state-registration.recordManualFilingOutcomeRecord the outcome of a Kreto operator completing a filing from the ops-fallback queue (AC-10). Writes the SAME evidence shape as the agentic path (actor=user, via='manual'). APPROVAL_REQUIRED — a human is doing the consequential act; UI-only, never MCP/CLI. Handler: Phase E.
Record the outcome of a Kreto operator completing a filing from the ops-fallback queue (AC-10). Writes the SAME evidence shape as the agentic path (actor=user, via='manual'). APPROVAL_REQUIRED — a human is doing the consequential act; UI-only, never MCP/CLI. Handler: Phase E.
state_registration.writeRequest body StateRegistrationRecordManualFilingOutcomeInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
registrationId | string | required | — |
attemptId | string | optional | — |
outcome | enum: "submitted" | "rejected" | "needs_more_info" | required | — |
confirmationNumber | string | optional | — |
feeAmountCents | integer | optional | — |
notes | string | optional | — |
Responses
- 200Successful invocation
StateRegistrationRecordManualFilingOutcomeOutputField Type Required Description registrationIdstringrequired — statusstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `state_registration.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:team-invitations6
POST/v1/team-invitations.createInvitationCreate a team-invitation row, generate a single-use accept token, and email it to the invitee. The accept link is never returned to the caller (ruling 97(a)); the response reports whether the email was sent.
Create a team-invitation row, generate a single-use accept token, and email it to the invitee. The accept link is never returned to the caller (ruling 97(a)); the response reports whether the email was sent.
employers.writeRequest body TeamInvitationsCreateInvitationInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string | optional | — |
email | string · email | required | — |
role | enum: "controller" | "viewer" | required | — |
Responses
- 200Successful invocation
TeamInvitationsCreateInvitationOutputField Type Required Description invitationobjectrequired — email_sentbooleanrequired — email_failure_reasonenum: "suppressed_non_prod" | "rate_limited" | "rejected" | "unavailable" | …optional — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `employers.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/team-invitations.listInvitationsList team invitations for an entity (defaults to caller's owned entity). Never returns plaintext tokens or token hashes.
List team invitations for an entity (defaults to caller's owned entity). Never returns plaintext tokens or token hashes.
employers.readRequest body TeamInvitationsListInvitationsInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string | optional | — |
status | enum: "pending" | "accepted" | "revoked" | "expired" | optional | — |
Responses
- 200Successful invocation
TeamInvitationsListInvitationsOutputField Type Required Description invitationsarray<object>required — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `employers.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/team-invitations.revokeInvitationRevoke a pending invitation. Idempotent for already-revoked/accepted rows (returns current status). Only entity owners may revoke.
Revoke a pending invitation. Idempotent for already-revoked/accepted rows (returns current status). Only entity owners may revoke.
employers.writeRequest body TeamInvitationsRevokeInvitationInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
invitation_id | string | required | — |
Responses
- 200Successful invocation
TeamInvitationsRevokeInvitationOutputField Type Required Description idstringrequired — statusstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `employers.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/team-invitations.resendInvitationRe-send a pending team invitation's e-mail. Mints a NEW single-use accept token and invalidates the previous one, so the earlier link stops working. The accept link is never returned to the caller (ruling 97(a)); the response reports whether the e-mail was sent. Expired, accepted and revoked invitations cannot be resent. Only entity owners may call.
Re-send a pending team invitation's e-mail. Mints a NEW single-use accept token and invalidates the previous one, so the earlier link stops working. The accept link is never returned to the caller (ruling 97(a)); the response reports whether the e-mail was sent. Expired, accepted and revoked invitations cannot be resent. Only entity owners may call.
employers.writeRequest body TeamInvitationsResendInvitationInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
invitation_id | string | required | — |
Responses
- 200Successful invocation
TeamInvitationsResendInvitationOutputField Type Required Description idstringrequired — statusstringrequired — expires_atstringrequired — email_sentbooleanrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `employers.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/team-invitations.acceptInvitationAccept a team invitation. Verifies token, expiry, self-acceptance, and email-mismatch guards. Inserts entity_members row on success. The caller MUST be signed in as the invited email.
Accept a team invitation. Verifies token, expiry, self-acceptance, and email-mismatch guards. Inserts entity_members row on success. The caller MUST be signed in as the invited email.
employers.writeRequest body TeamInvitationsAcceptInvitationInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
token | string | required | — |
Responses
- 200Successful invocation
TeamInvitationsAcceptInvitationOutputField Type Required Description invitation_idstringrequired — entity_idstringrequired — rolestringrequired — role_grantedbooleanrequired — existing_role_preservedbooleanrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `employers.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/team-invitations.getInvitationByTokenPublic lookup by plaintext token (hashes on read; never returns hash or plaintext). Used by /accept-team-invite/[token] for render.
Public lookup by plaintext token (hashes on read; never returns hash or plaintext). Used by /accept-team-invite/[token] for render.
employers.readRequest body TeamInvitationsGetInvitationByTokenInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
token | string | required | — |
Responses
- 200Successful invocation
TeamInvitationsGetInvitationByTokenOutputField Type Required Description invitationobjectrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `employers.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:tenants1
POST/v1/tenants.requestDeletionFile an APPROVAL_REQUIRED request to delete the caller's own tenant. Admin-only, human-only, and NON-DESTRUCTIVE: it creates a pending approval_requests row and an immutable tenant_deletion_requested audit event, and never deletes anything. The hard delete is performed by the retention executor after the grace window, and every delete path — this one or an out-of-band SQL session — writes a tenant_deleted audit event by database trigger (20260918000000, ruling 141(a)).
File an APPROVAL_REQUIRED request to delete the caller's own tenant. Admin-only, human-only, and NON-DESTRUCTIVE: it creates a pending approval_requests row and an immutable tenant_deletion_requested audit event, and never deletes anything. The hard delete is performed by the retention executor after the grace window, and every delete path — this one or an out-of-band SQL session — writes a tenant_deleted audit event by database trigger (20260918000000, ruling 141(a)).
admin.writeRequest body TenantsRequestDeletionInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
tenantId | string | required | — |
reason | string | required | — |
Responses
- 200Successful invocation
TenantsRequestDeletionOutputField Type Required Description approvalRequestIdstringrequired — tenantIdstringrequired — statusenum: "pending"required — retentionDaysintegerrequired — earliestPurgeAtstringrequired — auditEventIdstringrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
l2:vault4
POST/v1/vault.getPolicyRead the account's vault policy (who may reveal plaintext, per-portal MFA overrides, notification routing), the immutable hard floors, and the break-glass lock state. Use to check whether vault operations are currently halted for the tenant.
Read the account's vault policy (who may reveal plaintext, per-portal MFA overrides, notification routing), the immutable hard floors, and the break-glass lock state. Use to check whether vault operations are currently halted for the tenant.
admin.readRequest body VaultGetPolicyInputrequired
application/json
Responses
- 200Successful invocation
VaultGetPolicyOutputField Type Required Description policyobjectrequired — lockobjectrequired — vaultV1Enforcementbooleanrequired — - 400Validation error (input schema rejected). risk_level=autonomous.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.read`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/vault.setPolicyUpdate the account's configurable vault policy: reveal visibility, per-portal MFA strategy overrides, notification routing. The hard floors (money approval, freeze-on-first-failure) are not accepted by this method by construction.
Update the account's configurable vault policy: reveal visibility, per-portal MFA strategy overrides, notification routing. The hard floors (money approval, freeze-on-first-failure) are not accepted by this method by construction.
admin.writeRequest body VaultSetPolicyInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
revealVisibility | enum: "members" | "admins_only" | required | — |
mfaMatrixOverride | object | optional | — |
notificationRouting | object | optional | — |
Responses
- 200Successful invocation
VaultSetPolicyOutputField Type Required Description policyobjectrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/vault.breakGlassEmergency brake: immediately halt all credential decryption, use, rotation, and reveal for the tenant WITHOUT destroying key material. Reversible — restoring requires dual confirmation. Use on suspected compromise.
Emergency brake: immediately halt all credential decryption, use, rotation, and reveal for the tenant WITHOUT destroying key material. Reversible — restoring requires dual confirmation. Use on suspected compromise.
admin.writeRequest body VaultBreakGlassInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | required | — |
confirm | enum: true | required | — |
Responses
- 200Successful invocation
VaultBreakGlassOutputField Type Required Description lockedenum: truerequired — lockedAtstringrequired — alreadyLockedbooleanrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
POST/v1/vault.restoreSecond half of break-glass: lift the tenant-wide vault halt. Requires two confirmations from DIFFERENT humans (interim out-of-band dual-confirm until KRT-1554 step-up auth ships). The first call marks the restore pending; a second call by a different user completes it.
Second half of break-glass: lift the tenant-wide vault halt. Requires two confirmations from DIFFERENT humans (interim out-of-band dual-confirm until KRT-1554 step-up auth ships). The first call marks the restore pending; a second call by a different user completes it.
admin.writeRequest body VaultRestoreInputrequired
| Field | Type | Required | Description |
|---|---|---|---|
confirm | enum: true | required | — |
reason | string | optional | — |
Responses
- 200Successful invocation
VaultRestoreOutputField Type Required Description lockedbooleanrequired — restorePendingbooleanrequired — restoredbooleanrequired — secondConfirmerRequiredbooleanrequired — - 400Validation error (input schema rejected). risk_level=approval_required.
L2ErrorEnvelope - 401Unauthorized (missing or invalid bearer token)
L2ErrorEnvelope - 402Payment required — the tenant has no active subscription for this write-class operation (code `SUBSCRIPTION_REQUIRED`). The REST twin refuses the same way; see /api/notices/upload. Read-class operations are not gated.
L2ErrorEnvelope - 403Forbidden (missing scope or RLS denied). APPROVAL_REQUIRED ops additionally need a human approval token; see KRT-1554 step-up flow. Requires scope `admin.write`.
L2ErrorEnvelope - 429Rate limit exceeded — the same refusal the REST doors emit (`rateLimitRefusalBody`), with `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` and `Retry-After`. Bearer keys are metered per key; a response carrying `X-RateLimit-Enforced: false` was not counted (limiter store unavailable, fail-open).
MiscRateLimitRefusalResponse - 500Internal server error (`L2ServerError`), or — on the cookie-session leg only — a signed-in user with no profile gets the auth gate's `AccountSetupIncompleteResponse` (the dispatcher authenticates through `requireBearerOrSession`, whose session leg is `requireAuthAPI`).
one of (L2ServerError · AccountSetupIncompleteResponse)
My Compliance16
POST/api/my-compliance/signature-requests/{id}/fresh-linkGet a fresh signing link
Replace a PENDING, unexpired signing link on one of the employer's own authorizations with a new one and return it once (no email). The previous link stops working; the expiry is unchanged. Shares the 5-send cap with the firm's Resend. Cookie session only, employer role only. Unknown, foreign and other-employer ids share one 404 body.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Signing request (signature_requests) UUID |
Responses
- 200Link rotated and returned once
FreshSigningLinkResponseField Type Required Description okenum: truerequired — urlstringrequired The new signing link, returned this once. The previous link stops working. Never stored, logged or shown again. expires_atstringrequired Unchanged — the new link keeps the request's expiry. - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — employer access required
MiscUnauthorizedResponse - 404Signing request not found (or not the caller's)
FreshSigningLinkNotFound - 409Not renewable — signed, expired, cancelled, or changed
FreshSigningLinkRefusal - 429This request has reached its send limit
FreshSigningLinkLimit - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse) - 503App origin not configured, or unavailable on this deployment
FreshSigningLinkUnavailable
GET/api/my-compliance/letterheadGet the employer's letterhead
The letterhead every letter from a standalone employer's workspace prints, and which required fields are still empty. Cookie session only, employer role only. A CPA firm's client gets the same 404 as an absent workspace — its letters carry the firm's letterhead.
Responses
- 200The letterhead
EmployerLetterheadResponseField Type Required Description namestringrequired The workspace's name (from sign-up); read-only here. letterhead_address_line1stringrequired — letterhead_address_line2stringrequired — letterhead_citystringrequired — letterhead_statestringrequired — letterhead_postal_codestringrequired — letterhead_phonestringrequired — letterhead_emailstringrequired — letterhead_signer_namestringrequired — letterhead_signer_titlestringrequired — missingarray<string>required Required letterhead fields still empty. Non-empty means every new draft is marked letterhead-incomplete and cannot be approved. - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — employer access required
MiscUnauthorizedResponse - 404No letterhead door for this workspace
EmployerLetterheadNotFound - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PUT/api/my-compliance/letterheadUpdate the employer's letterhead
Edit a standalone employer's letterhead. Absent key = untouched; an empty string or null clears the field. Validated by the same rules as the firm's letterhead. Audited (field names, never values). Cookie session only, employer role only; a CPA firm's client gets the 404.
Request body EmployerLetterheadUpdateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
letterhead_address_line1 | string | optional | — |
letterhead_address_line2 | string | optional | — |
letterhead_city | string | optional | — |
letterhead_state | string | optional | — |
letterhead_postal_code | string | optional | — |
letterhead_phone | string | optional | — |
letterhead_email | string | optional | — |
letterhead_signer_name | string | optional | — |
letterhead_signer_title | string | optional | — |
Responses
- 200Letterhead saved
EmployerLetterheadResponseField Type Required Description namestringrequired The workspace's name (from sign-up); read-only here. letterhead_address_line1stringrequired — letterhead_address_line2stringrequired — letterhead_citystringrequired — letterhead_statestringrequired — letterhead_postal_codestringrequired — letterhead_phonestringrequired — letterhead_emailstringrequired — letterhead_signer_namestringrequired — letterhead_signer_titlestringrequired — missingarray<string>required Required letterhead fields still empty. Non-empty means every new draft is marked letterhead-incomplete and cannot be approved. - 400Not a valid letterhead edit
EmployerLetterheadInvalid - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — employer access required
MiscUnauthorizedResponse - 404No letterhead door for this workspace
EmployerLetterheadNotFound - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/employer-cpa-attachmentsGet the employer's accountant attachment
The 'Your accountant' card state for a standalone employer (or the attached accountant). Cookie session only. Flag off, wrong plane or a firm member with no attachment answer the one 404 marker.
Responses
- 200Card state
EmployerCpaStatusField Type Required Description kindenum: "self_tenant" | "cpa_managed_client" | "firm_member"required — can_invitebooleanrequired — attachmentobjectrequired — - 401Unauthorized
MiscUnauthorizedResponse - 404Not found
EmployerCpaNotFound - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/employer-cpa-attachmentsInvite an accountant
A client_user of a standalone ('self') workspace invites ONE accountant by email. Refused when the workspace is firm-managed (403 NOT_SELF_TENANT), already has a live accountant (409 CPA_ALREADY_ATTACHED) or a pending invite (409 CPA_INVITE_PENDING).
Request body EmployerCpaInviteBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
email | string | required | — |
firm_name | string | required | — |
Responses
- 201Invite created
EmployerCpaInviteResponseField Type Required Description inviteobjectrequired — - 400Invalid body
EmployerCpaRefusal - 401Unauthorized
MiscUnauthorizedResponse - 403Not a standalone employer
EmployerCpaRefusal - 404Not found
EmployerCpaNotFound - 409Already attached / invite pending
EmployerCpaRefusal - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
DELETE/api/employer-cpa-attachmentsRemove the accountant (or cancel the pending invite)
Detach: soft-removes the accountant's profile in this workspace (their earlier sessions refuse) and the workspace reads standalone again; a pending invite is cancelled instead. Audited.
Responses
- 200Detached or cancelled
EmployerCpaDetachResponseField Type Required Description outcomeenum: "detached" | "cancelled"required — - 401Unauthorized
MiscUnauthorizedResponse - 403Not a standalone employer
EmployerCpaRefusal - 404Nothing attached
EmployerCpaNotFound - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/employer-cpa-attachments/token/{token}Look up an accountant invite by token (public)
Public token plane: the URL-path token is the credential. Every non-live state (never issued, revoked, expired, accepted, detached, malformed) answers the one closed-invite response.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
token | path | string | required | — |
Responses
- 200Live invite
EmployerCpaInvitePeekField Type Required Description invitationobjectrequired — - 404Closed invite
EmployerCpaClosedInvite - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/employer-cpa-attachments/token/{token}/acceptAccept an accountant invite
The signed-in invitee (no Kreto profile yet) becomes the workspace's accountant (cpa_member). Refused with 403 CPA_HAS_WORKSPACE when the login already has a profile, 403 email_mismatch for another address, 409 already_accepted on a re-click; closed tokens answer the one closed-invite response.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
token | path | string | required | — |
Responses
- 200Attached
EmployerCpaAcceptResponseField Type Required Description successenum: truerequired — redirectstringrequired — - 401Unauthorized
MiscUnauthorizedResponse - 403Refused
EmployerCpaRefusal - 404Closed invite
EmployerCpaClosedInvite - 409Already accepted / already attached
EmployerCpaRefusal - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/my-compliance/profileRead the caller's business profile (employer-tier)
Returns the entity row scoped to the authenticated user via entity_members. Works for both cpa_member (returns first entity) and client_user (returns their single entity). Bearer auth requires employers.read scope.
Responses
- 200Business profile
MyComplianceProfileField Type Required Description entity_idstring · uuidrequired — legal_namestringrequired — ein_last4stringrequired — primary_statestringrequired — compliance_scorenumberrequired — compliance_score_statusenum: "scored" | "not_scored" | "unavailable"required The compliance monitor's verdict on `compliance_score`: `scored` (a number was measured), `not_scored` (nothing on file to measure), or `unavailable` (the monitor read refused). Never a manufactured 0. created_atstring · date-timerequired — - 401Unauthorized
MyComplianceError - 404No business profile linked to this user
MyComplianceError - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth gate's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PATCH/api/my-compliance/profileUpdate business profile fields
Partial update of the caller's entity. Sends only the fields that changed. EIN is stored as last-4 only (PII rule). Bearer auth requires employers.write scope, which only CPA-tier keys carry; an employer-tier key cannot call this.
Request body MyComplianceProfilePatchrequired
| Field | Type | Required | Description |
|---|---|---|---|
legal_name | string | optional | — |
ein_last4 | string | optional | — |
primary_state | string | optional | — |
payroll_provider | enum: "adp" | "gusto" | "paychex" | "quickbooks" | … | optional | The employer's payroll provider (owner walk S6). `manual` = payroll run in-house; `none` = no payroll yet; `other` requires `payroll_provider_other`. Any other value clears that name. |
payroll_provider_other | string | optional | The provider's name when `payroll_provider` is `other`; only sent together with `payroll_provider`. |
Responses
- 200Profile updated
MyComplianceProfilePatchResponseField Type Required Description okbooleanrequired — entityobjectrequired — draftsRequeuedintegeroptional Drafts re-queued because this save put a legal name on a record that had none. Absent when the save was not a blank-to-name save. draftsRequeueErrorenum: "requeue_failed"optional The name was saved but some or all of the waiting drafts could not be re-queued (draftsRequeued counts the ones that were). Generate the rest again by hand. - 400Validation error
MyComplianceError - 404No business profile linked to this user
MyComplianceError - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth gate's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/my-compliance/auditAudit log for the caller's entity
Read-only timeline of audit_events filtered to the entity the caller belongs to. Composite cursor pagination — pass next_cursor back as `before` to fetch the next page. Bearer auth requires audit.read scope.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | optional | — |
before | query | string | optional | Composite cursor in the form '<iso8601>|<uuid>'. Legacy ISO-only cursors also accepted for backwards compatibility. |
Responses
- 200Audit log page
MyComplianceAuditListField Type Required Description eventsarray<MyComplianceAuditEvent>required — next_cursorstringrequired — entity_idsarray<string · uuid>required — - 400Invalid cursor
MyComplianceError - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth gate's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/my-compliance/api-tokensList the caller's non-revoked Bearer tokens
Returns API keys created by the authenticated user, filtered to their tenant. Plaintext is NEVER included — only key_prefix + metadata. Bearer auth NOT supported on this route (cookie-session only) to prevent a token from listing or mass-revoking peers.
Responses
- 200Token list (non-revoked only)
MyComplianceApiTokenListField Type Required Description tokensarray<MyComplianceApiToken>required — - 401Unauthorized
MyComplianceError - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/my-compliance/api-tokensMint a new employer-scoped Bearer token
Creates a new api_keys row via the same hash-on-write pipeline as /api/api-keys, but with the scopes constrained to the employer allow-list (notices.read, employers.read, payroll.read, audit.read). Plaintext is returned ONCE in `plaintext` — store it now; we keep only the bcrypt hash. RLS migration 20260615000000 enforces the scope subset at the DB layer too (defense in depth).
Request body MyComplianceApiTokenCreaterequired
| Field | Type | Required | Description |
|---|---|---|---|
name | string | optional | — |
scopes | array<string> | required | — |
expires_at | string · date-time | optional | Optional future expiry. Omitted or null = 90 days from mint (DEFAULT_TOKEN_LIFETIME_DAYS); a token never mints without one. |
Responses
- 200Token created. Plaintext returned ONCE.
MyComplianceApiTokenCreatedField Type Required Description okbooleanrequired — plaintextstringrequired — tokenobjectrequired — - 400Refused before any key material exists: no scopes (code SCOPES_REQUIRED), a malformed or all-unmintable scopes array, or an expires_at that is not a future ISO timestamp (invalid_expires_at).
MyComplianceError - 500Failed to create token
one of (SanitizedServerError · AccountSetupIncompleteResponse)
DELETE/api/my-compliance/api-tokensRevoke one of your own Bearer tokens
Soft-delete via revoked_at timestamp; audit traces survive. Scoped to the caller's own tokens by created_by_user_id — you cannot revoke peers' tokens even within the same tenant.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | query | string · uuid | required | UUID of the api_keys row to revoke |
Responses
- 200Token revoked (idempotent — re-revoking is a no-op 200)
MyComplianceOkResponseField Type Required Description okbooleanrequired — - 400Missing token id
MyComplianceError - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/my-compliance/poa/{id}/signed-pdfDownload the signed IRS 8821 for one of the caller's employers
Streams the signed Form 8821 PDF inline. The authorization is resolved through the caller's OWN entity_members set — never from the path alone — so a POA belonging to a sibling employer in the same CPA tenant is refused. Not-found and not-yours deliberately return the SAME 404 so the route is not an existence oracle for other employers' POA ids. Response is tax-authorization PII: Cache-Control is private, no-store.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | poa_authorizations UUID |
Responses
- 200Signed 8821 PDF
string · binary - 400Invalid authorization id (not a UUID)
MyComplianceError - 401Unauthorized
MyComplianceError - 404Authorization not found, not the caller's, or not yet signed
MyComplianceError - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth gate's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/my-compliance/state-registrationsStart a state registration for the caller's own business
Employer (client_user) session only. Creates one open state-registration record for the caller's own business through the same store as the CPA manual start. One open record per business and state: a second start for the same state answers 409 naming the existing record. Only (state, requirement) pairs Kreto supports are accepted.
Request body EmployerRegisterStateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
state | string | required | — |
requirement | enum: "sui_registration" | "withholding_registration" | "workers_comp" | "pfml" | … | required | — |
entity_id | string · uuid | optional | — |
Responses
- 201Record created
EmployerRegisterStateCreatedField Type Required Description recommendationobjectrequired — feeSentencestringrequired — - 400Invalid body, or an unsupported state/requirement
EmployerRegisterStateError - 401Unauthorized
EmployerRegisterStateError - 403Not an employer session
EmployerRegisterStateError - 404Business not found (the one marker for anything out of scope)
EmployerRegisterStateError - 409An open registration already exists for this state
EmployerRegisterStateAlreadyOpen - 500Server error — sanitized, or the auth gate's AccountSetupIncompleteResponse for a session with no profile
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Notices68
GET/api/notices/{id}/auditGet notice audit trail
Returns the append-only audit chain for a single notice, including every event's hash links and a `chain_valid` flag attesting that the SHA-256 chain verifies cryptographically. `chain_valid` is `null` when the chain is empty — an empty chain is not a verified chain — so it is a nullable boolean, never `true` beside `total: 0`.
audit.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Audit chain + integrity attestation
NoticeAuditResponseField Type Required Description okenum: truerequired — notice_idstring · uuidrequired — entriesarray<AuditEntry>required — chain_validbooleanrequired null when the chain is empty — an empty chain is not a verified chain chain_break_atintegerrequired — totalintegerrequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/notices/{id}/document-urlGet signed URL for notice document
Returns a 1-hour signed URL for the original notice document. Tenant-scoped: only notices in the caller's tenant resolve to a URL.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Signed URL (or null if no document is attached)
NoticeDocumentUrlResponseField Type Required Description urlstringrequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant — the same landing for an unknown id and for a foreign-tenant id
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (NoticeDocumentUrlServerError · AccountSetupIncompleteResponse)
GET/api/notices/{id}/draft-dataGet latest notice draft
Returns the latest resolution draft for a notice — content, summary, structured action_items, response_type, and status. Reads the canonical `resolution_drafts` table.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Latest draft (or null fields if none exists)
NoticeDraftDataResponseField Type Required Description contentstringrequired — summarystringrequired — action_itemsarray<string>required — response_typestringrequired — statusstringoptional — created_atstring · date-timeoptional — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PATCH/api/notices/{id}/entityReassign notice to another entity
Re-attribute a notice to a different entity. Both notice and target entity must be in the caller's tenant; the entity is verified before the update so cross-tenant re-attribution is impossible.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body NoticeEntityPatchBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
Responses
- 200Updated
NoticeEntityPatchResponseField Type Required Description successenum: truerequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice or target entity not found in caller's tenant
NoticeNotFoundResponse - 422The chosen entity is the firm's own record (`code: employer_is_firm_self`) — it cannot be a notice's employer; nothing was written
NoticeEmployerIsFirmSelfResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/notices/{id}/letterGet signed URL for the stored response letter
Returns a short-lived signed URL for the STORED response letter — bytes the browser did not compose — together with the content hash they were filed under. 404 when no letter has been stored for the notice, and a voided or deleted artifact reads as absent rather than as a stale URL.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Signed URL + the artifact's identity
NoticeLetterUrlResponseField Type Required Description urlstringrequired — document_idstring · uuidrequired — content_hashstringrequired — versionintegerrequired — rendered_atstring · date-timerequired null when the running database predates migration 20260977000000 and the binding columns were unreadable (§SCHEMA-FORWARD READS). - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope or non-CPA session
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant, or no stored letter for it
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/letterRender and store the notice's response letter
Renders the notice's current resolution draft into the agency-bound letter SERVER-SIDE, hashes the bytes that are stored, files them as a `documents` row (kind `response`, `checksum` set) and binds that row to the draft version. Idempotent by content hash: identical bytes reuse the existing row rather than inserting a second one. The render is behind the SAME refusal predicate as approval — a draft carrying a redaction placeholder, or blocked on payroll, is refused 409 with no override field.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200The stored artifact's identity
NoticeLetterArtifactResponseField Type Required Description document_idstring · uuidrequired — content_hashstringrequired sha256 of the bytes that were STORED, hex versionintegerrequired resolution_drafts.version these bytes were rendered from substitutionsarray<string>required Distinct U+XXXX code points the WinAnsi seam had to replace before drawing. A substituted character on a filed letter is a fact a human signs off on, not a log line — hence it is in the response. substitution_countintegerrequired — reused_existing_documentbooleanrequired True when identical bytes were already stored and the existing documents row was reused (idempotent by content hash). - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope or non-CPA session
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant, or the notice has no draft — the same landing for an unknown id and for a foreign-tenant id
NoticeNotFoundResponse - 409A gate held: the draft is blocked (`DRAFT_BLOCKED`) or the notice's employer is not confirmed yet (`ENTITY_UNCONFIRMED`)
NoticeLetterBlockedResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/confirm-stateConfirm notice state
Confirm or override the state on a notice. Used when AI extraction misses or misclassifies the state and a human corrects it. The override is logged to audit_events for compliance traceability.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body NoticeConfirmStateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
confirmedState | string | required | — |
Responses
- 200Confirmed
NoticeSuccessResponseField Type Required Description successenum: truerequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/notices/{id}/documentDownload notice document
Streams the original notice document (PDF/PNG/JPEG/TIFF) inline. Browser sees a Kreto URL, not a Supabase storage URL.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Binary document content (PDF/PNG/JPEG/TIFF) streamed inline. OpenAPI generator requires schemas for each content type — using z.string() (binary) as a generic placeholder; clients should consume the response stream directly per Content-Type header.
string - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
- 403Forbidden — missing required scope
- 404Document not found / notice not in caller's tenant
- 429Rate limit exceeded
- 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/notices/{id}/supplementalGet notice supplemental data
Returns the active work_item, document_requests, and messages associated with a notice. All queries tenant-scoped.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Supplemental bundle
NoticeSupplementalResponseField Type Required Description work_itemNoticeWorkItemrequired — document_requestsarray<NoticeDocumentRequest>required — messagesarray<NoticeMessage>required — sender_namesobjectrequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/notices/{id}/statusGet notice pipeline status
Returns pipeline status + classification result for a notice (notice_type_classified, classification_confidence, protest_deadline, financial_impact, protest_recommended, assigned_rate, pipeline_status).
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Pipeline status
NoticeStatusResponseField Type Required Description idstring · uuidrequired — notice_typestringrequired — statestringrequired — pipeline_statusstringrequired — notice_type_classifiedstringrequired — classification_confidencenumberrequired — protest_deadlinestring · daterequired — financial_impactnumberrequired — protest_recommendedbooleanrequired — assigned_ratenumberrequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/statusUpdate notice triage fields
Update notice triage fields (assigned_rate, is_new_employer). is_new_employer is stored inside extracted_json (no dedicated column).
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body NoticeStatusPostBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
assigned_rate | number | optional | — |
is_new_employer | boolean | optional | — |
Responses
- 200Updated
NoticeStatusPostResponseField Type Required Description okenum: truerequired — updatedNoticeStatusPostUpdatedrequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/entity-match-resolveResolve fuzzy entity match
Resolve a fuzzy entity-match candidate set for a notice. `action=confirmed` assigns the notice to an existing entity (verified to belong to caller's tenant). `action=create_new` creates a new entity in the notice's tenant and assigns the notice to it.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body NoticeEntityMatchResolveBodyrequired
application/json
Responses
- 200Resolved
NoticeEntityMatchResolveResponseField Type Required Description successenum: truerequired — entity_idstring · uuidrequired — actionenum: "confirmed" | "create_new"required — entity_namestringoptional — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice or resolved entity not found in caller's tenant
NoticeNotFoundResponse - 422The chosen entity is the firm's own record (`code: employer_is_firm_self`) — it cannot be a notice's employer; nothing was written
NoticeEmployerIsFirmSelfResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/notices/{id}/similarFind similar resolved notices
Find anonymized resolution patterns + similar resolved notices for a given notice. The source notice lookup is tenant-scoped; the pattern + outcomes lookups are intentionally cross-tenant (anonymized aggregate, no PII).
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Resolution pattern + anonymized similar notices
NoticeSimilarResponseField Type Required Description patternResolutionPatternrequired — similar_countintegerrequired — similar_noticesarray<SimilarNotice>required — recommendationstringrequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/outcomeRecord notice outcome
Record the resolution outcome when a notice is resolved. Inserts a notice_outcomes row, optionally captures classification feedback, closes the notice, and aggregates into the cross-tenant resolution_patterns inventory for similar-notice recommendations.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body NoticeOutcomePostBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
resolution_type | string | required | — |
resolution_notes | string | optional | — |
resolution_method | string | optional | — |
corrected_type | string | optional | — |
original_amount | number | optional | — |
final_amount | number | optional | — |
documents_used | array<DocumentRef> | optional | — |
documents_missing | array<DocumentRef> | optional | — |
deadline_was_correct | boolean | optional | — |
actual_deadline | string · date | optional | — |
agency_response_date | string · date | optional | — |
Responses
- 200Outcome recorded
NoticeOutcomePostResponseField Type Required Description okenum: truerequired — outcome_idstring · uuidrequired — savingsnumberrequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — not a CPA-class session nor the self-tenant founder
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant (or, for the self-tenant founder, outside its entity scope)
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/interpretInterpret notice PDF (Claude)
Synchronously interpret the notice PDF using Claude. Downloads the raw PDF, runs PII-redacted extraction + classification, validates against quality gates (returns 422 if they fail), creates artifact + lineage records, and updates the notice with the result.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Interpreted
NoticeInterpretResponseField Type Required Description okenum: truerequired — noticeIdstring · uuidrequired — artifactIdstring · uuidrequired — interpretationobjectrequired — qualityCheckobjectrequired — metadataobjectrequired — notice_updatedbooleanrequired — notice_update_refusedenum: "terminal" | "write_failed" | "guard_state_unreadable"optional — approval_supersededenum: trueoptional — audit_event_pendingenum: trueoptional — - 400Validation error / no storage path / PDF interpretation failed
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant
NoticeNotFoundResponse - 422Quality gates failed (interpretation incomplete)
NoticeInterpretQualityFailResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/classifyClassify notice text/file
Classify a notice from raw text (JSON body) or file upload (multipart/form-data). Runs PII redaction first, then LLM classification with a 15-second timeout. Cookie-session callers go through the Stripe subscription gate; Bearer-key callers do not.
notices.writeRequest body NoticeClassifyJsonBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
text | string | optional | — |
entity_id | string · uuid | optional | — |
Responses
- 200Classification result
NoticeClassifyResponseField Type Required Description notice_typestringrequired — jurisdictionstringoptional — deadlinestring · dateoptional — severitystringoptional — confidencenumberrequired — categorystringoptional — subcategorystringoptional — extracted_factsobjectrequired — enrichmentobjectrequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 402Subscription required (cookie-session callers only)
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error / classification timeout
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/reopenRe-open a closed notice
CPA-only. Moves a closed (actioned) notice back to filed with a reason code; the filing is kept, the current outcome goes back to pending, and one hash-chained audit row records the move.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
reason | enum: "outcome_recorded_in_error" | "agency_reversed_decision" | "agency_requested_more" | "other" | required | — |
Responses
- 200Moved; the notice's new lifecycle stage
NoticeEndingMoveResponseField Type Required Description okenum: truerequired — lifecycle_statestringrequired — - 400Invalid JSON, or the reason is missing / not a listed code
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — a CPA-firm role is required
NoticeUnauthorizedResponse - 404Notice not found (unknown, foreign or malformed id — one body)
NoticeNotFoundResponse - 409Refused at the notice's current stage (NOTICE_NOT_CLOSED)
NoticeEndingMoveRefusal - 500Server error — sanitized, or the auth wrapper's AccountSetupIncompleteResponse
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/withdrawWithdraw a filed response
CPA-only. Moves a filed notice back to approved with a reason code; the filing is cleared, the approval kept, and one hash-chained audit row records the move.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
reason | enum: "filed_in_error" | "wrong_letter_filed" | "agency_asked_to_resubmit" | "other" | required | — |
Responses
- 200Moved; the notice's new lifecycle stage
NoticeEndingMoveResponseField Type Required Description okenum: truerequired — lifecycle_statestringrequired — - 400Invalid JSON, or the reason is missing / not a listed code
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — a CPA-firm role is required
NoticeUnauthorizedResponse - 404Notice not found (unknown, foreign or malformed id — one body)
NoticeNotFoundResponse - 409Refused at the notice's current stage (NOTICE_CLOSED, FILING_REQUIRED, OUTSIDE_RESPONSE)
NoticeEndingMoveRefusal - 500Server error — sanitized, or the auth wrapper's AccountSetupIncompleteResponse
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/withdraw-approvalWithdraw the approval of an unsent letter
CPA-only. Takes back the approval of a letter that has not been sent or filed: the notice returns to drafting, the approved letter of record is kept as its version marked 'withdrawn' (the next draft is the next version), and one hash-chained audit row records who, when and the required reason — in one transaction.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | required | — |
Responses
- 200Withdrawn; the notice's new lifecycle stage and the withdrawn version
NoticeWithdrawApprovalResponseField Type Required Description okenum: truerequired — lifecycle_statestringrequired — withdrawn_draft_versionintegerrequired — - 400Invalid JSON, or the reason is missing or shorter than 10 characters
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — a CPA-firm role is required
NoticeUnauthorizedResponse - 404Notice not found (unknown, foreign or malformed id — one body)
NoticeNotFoundResponse - 409Refused (LETTER_FILED, LETTER_SENT_FOR_SIGNATURE, NOTICE_CLOSED, NOT_APPROVED)
NoticeWithdrawApprovalRefusal - 500Server error — sanitized, or the auth wrapper's AccountSetupIncompleteResponse
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/restoreRestore a document marked not a notice
CPA-only. Clears the not-a-notice flag (confirmed or proposed), puts the notice back at the stage it held before the flag, writes one audit row, and runs reading again. The body is optional; a given reason is the audit rationale (10–500 characters).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body object
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | optional | — |
Responses
- 200Restored; the notice's pipeline stage and whether reading was queued again
NoticeRestoreResponseField Type Required Description okenum: truerequired — pipeline_statusstringrequired — reading_requeuedbooleanrequired — - 400Invalid JSON, or a given reason is not 10 to 500 characters
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — a CPA-firm role is required
NoticeUnauthorizedResponse - 404Notice not found (unknown, foreign or malformed id — one body)
NoticeNotFoundResponse - 409Nothing to restore — the notice is not marked not a notice (NOTICE_NOT_JUNK)
NoticeRestoreRefusal - 500Server error — sanitized, or the auth wrapper's AccountSetupIncompleteResponse
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/merge-duplicateMerge a possible duplicate into the earlier notice
Ruling Q21. Re-checks that both notices share agency, account number, notice date and amount; keeps the earlier notice, moves the later one's arrivals onto it and dismisses the later one.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
other_notice_id | string · uuid | required | — |
Responses
- 200Merged; the kept and the merged notice ids
NoticeMergeDuplicateResponseField Type Required Description okenum: truerequired — kept_notice_idstring · uuidrequired — merged_notice_idstring · uuidrequired — arrivals_movedintegerrequired — documents_movedintegerrequired — - 400Invalid JSON, or other_notice_id missing
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — a CPA-firm role is required
NoticeUnauthorizedResponse - 404Notice not found (unknown, foreign or malformed id)
NoticeNotFoundResponse - 409Refused — not a possible duplicate, or the later notice was already worked on
NoticeMergeDuplicateRefusal - 500Server error — sanitized, or the auth wrapper's AccountSetupIncompleteResponse
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/notices/{id}/draftGet notice draft
Returns the latest resolution draft for a notice (the canonical resolution_drafts table, falling back to an inline draft on notices.extracted_json). Tenant-scoped.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Latest draft + evidence package
NoticeDraftGetResponseField Type Required Description draftNoticeDraftRecordrequired — evidence_packageNoticeEvidencePackagerequired — notice_deadlinestring · daterequired — sourceenum: "resolution_drafts" | "extracted_json"required — signature_requestsarray<NoticeSignatureRequestSummary>optional — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice or draft not found in caller's tenant
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/draftGenerate notice draft
Generate a new draft via the pipeline draft generator. Cookie-session callers go through the Stripe subscription gate; Bearer-key callers do not.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Draft generated
NoticeDraftPostResponseField Type Required Description draftNoticeDraftRecordrequired — evidence_packageNoticeEvidencePackagerequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 402Subscription required (cookie-session callers only)
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant
NoticeNotFoundResponse - 409Generation produced a BLOCKED draft (missing payroll, a redaction placeholder, or the manual-review placeholder rather than a letter). The row exists and is named by `draft_id`, but it is not a letter and cannot be approved or filed until it is regenerated. Or (`TRACKED_NOT_CHECKED`): the notice is tracked, not checked (X1), so no one-click draft is written. Write the response by hand instead (PATCH). Nothing is written. Or (`FACTS_UNCONFIRMED`): the CPA has not confirmed what Kreto read (employer, notice type, deadline) — POST /api/notices/{id}/confirm-facts first. Asked after the not-found wall, X1 and the employer refusal. Nothing is written. Or (`DRAFT_JOB_LIVE`, in `error`): a draft-producing job is already queued or running for this notice (`draft_job`); poll the detail path's `draft_job` until `none`. Nothing is written.
one of (NoticeDraftBlockedResponse · NoticeDraftTrackedNotCheckedResponse · NoticeDraftFactsUnconfirmedResponse · NoticeDraftJobLiveResponse) - 422Generation refused as a human gate, not a fault: the draft is waiting on a fact only a person can supply (`needs_input.kind`; today the employer's legal name, added on the record `entity_id` names), or the output guard refused a letter citing evidence this notice does not have (`unbacked_evidence_claim`) or claiming timeliness on a passed or unknown deadline (`untimely_protest_claim`).
NoticeDraftGenerationRefusal - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error / draft generation failed / the draft was generated but its evidence package could not be created (never a 200 with `evidence_package: null`)
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PATCH/api/notices/{id}/draftUpdate notice draft
Update the most recent draft's content. Tenant-scoped both on read and update — root-cause fix vs pre-Phase-4 which only filtered by id.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body NoticeDraftPatchBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
content | string | required | — |
Responses
- 200Draft updated
NoticeDraftPatchResponseField Type Required Description draftNoticeDraftRecordrequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope, or the caller may read this draft but is not permitted to change it (code DRAFT_UPDATE_FORBIDDEN: the update matched zero rows and nothing was saved), or — on the FIRST save, before any draft exists — the caller's role may not write drafts (same code DRAFT_UPDATE_FORBIDDEN; refused before any write)
NoticeUnauthorizedResponse - 404No draft found in caller's tenant
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/notices/{id}/resolution-packageGet notice resolution package
Returns the most recent resolution package for a notice, or a `not_generated` stub when the caller's own notice has none yet. An unknown, soft-deleted, out-of-scope or cross-tenant notice id answers 404 with one body (the same one POST answers). Tenant-scoped.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Existing package, or the 'not_generated' stub for the caller's own notice that has no package yet
ResolutionPackageGetResponse - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant — one body for an unknown, soft-deleted, out-of-entity-scope or cross-tenant id (no existence oracle)
ResolutionPackageNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/resolution-packageGenerate notice resolution package
Generate a resolution package after confirming all required documents are present. Returns 400 if any are missing. If `protest_recommended` on the notice, generates a protest letter via Claude (PII-redacted) and bundles evidence; otherwise generates a `verification_complete` summary. Creates a submissions row and updates notice status.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Package generated
ResolutionPackagePostResponseField Type Required Description package_idstring · uuidrequired — statusenum: "generated"required — package_typeenum: "protest_letter" | "verification_complete"required — documentsarray<ResolutionPackageDocument>required — - 400Validation error / required documents missing
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403A valid Bearer key without `notices.write`, a session that must complete an MFA step-up (`mfa_required`), or `role_forbidden` — a non-CPA cookie role, refused before any read, write or letter generation (the same body for every id)
NoticesWriteForbiddenResponse - 404Notice or entity not found in caller's tenant
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/notices/{id}/detailGet notice detail
Returns the full notice + associated entity + audit_events + pipeline events + signed document URL + payroll context. Auto-applies any existing entity-level payroll verification when the tax year matches (re-uses prior payroll uploads across notices for the same entity).
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Notice detail bundle
NoticeDetailResponseField Type Required Description noticeobjectrequired The notice row projected onto the route's response allowlist. Carries `has_intake_raw` (boolean — a source document exists; open it via GET /api/notices/{id}/document) and never a storage path (`intake_raw_storage_path` is server-side only since fold-82). Owner ruling M3: on the firm plane it also carries `owner` — `{ owner_user_id: string | null, owner_label: string }`, the one owner label ("Unassigned", the member's name or email, or "Former member"); absent on the employer plane or when the owner read degraded. Walk-gap 4: on the employer plane it carries `cpa_people` instead — `{ contact: { display_name: string | null, email: string | null } | null, letter_signer: { name: string | null, title: string | null } | null }`: the notice owner (the employer's contact for this notice; never a user id) and the complete letterhead's signer; absent on the firm plane. Q7 / S-12 (fold-86): on the employer plane, until `approval_status = 'approved'`, the computed figure keys (potential_savings_usd, actual_savings_usd, auto_/payroll_/smart_verification) are absent and `figure_held: true` is set. I-7 (ruling Q23): it also carries `multi_notice` — on the firm plane `{ flagged: boolean, reasons: string[], resolution: 'split' | 'single' | null, split_from_notice_id: string | null, split_from_display_id: string | null }` (flagged = may hold more than one notice and not yet resolved; its deadline is not trusted); `null` on the employer plane or when the read degraded. entityobjectrequired — cpa_membersarray<object>required — eventsarray<object>required — pipeline_eventsarray<object>required — document_urlstringrequired — payrollobjectrequired — entity_payrollobjectrequired — payroll_connectedbooleanrequired — payroll_live_connectionbooleanrequired — holiday_rollobjectoptional — deadline_entriesobjectrequired — employerNoticeEmployerAttributionrequired — has_cpabooleanrequired — employer_fills_letterbooleanrequired — may_override_payrollbooleanrequired — employer_filing_reportone of (object · object · null)required — source_documentNoticeSourceDocumentrequired — parent_noticeNoticeLinkedNoticerequired — child_noticesarray<NoticeLinkedNotice>required — claim_sides_linkNoticeClaimSidesLinkrequired — as_ofstring · date-timerequired The server's clock when this body was built (ISO-8601, one read per response). Deadline countdowns and urgency bands are computed against this, never the client clock. arrivalsarray<object>required — junk_duplicate_ofobjectoptional — possible_duplicateobjectrequired — draft_jobobjectrequired — cross_reference_jobobjectrequired — signatureobjectrequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/detailAdd note to notice timeline
Append a note to the notice's audit timeline. Tenant_id always sourced from the auth context — never from the body — to prevent cross-tenant audit writes (Gemini PR #471 review fix).
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body NoticeDetailPostBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
note | string | required | — |
Responses
- 200Note appended
NoticeOkResponseField Type Required Description okenum: truerequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PATCH/api/notices/{id}/detailRefuses notice-status transitions (use /file or the close gate)
REFUSES every notice-status transition, by design. This endpoint owns none of the seven `notice_status` values: `filed` belongs to POST /api/notices/{id}/file (which records the confirmation number, `filed_at` and a hash-chained audit entry, and verifies the notice was approved), `closed` belongs to the close gate on the notice, and the remaining five are pipeline/derived states no control hand-edits. Every well-formed request therefore answers 409 with a `code` naming the door that owns the transition; a value outside the enum answers 400. The operation is retained because removing a published operation is a breaking SDK change, and GET on this path is load-bearing. (Batch-12 B12-1: it previously wrote `status` through with none of the filing controls.)
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body NoticeDetailPatchBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
status | enum: "open" | "in_progress" | "needs_review" | "waiting_client" | … | required | — |
Responses
- 400Invalid path parameter, malformed JSON, or a `status` outside the `notice_status` enum.
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 409Always, for any valid `status`. `code` names the door that owns the transition: `FILING_ROUTE_REQUIRED` (use POST /api/notices/{id}/file) or `STATUS_NOT_EDITABLE_HERE`.
NoticeStatusRefusal - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/notices/{id}/required-documentsGet required-documents checklist
Returns the document checklist required to resolve the notice. Each entry tagged status=uploaded | on_file | not_uploaded based on prior direct uploads, entity-level documents, payroll-on-file (tax-year + state matched), or existing payroll_verification.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Checklist + per-doc status
RequiredDocumentsGetResponseField Type Required Description notice_typestringrequired — rating_periodstringrequired — rating_period_yearintegerrequired — payroll_neededbooleanrequired — descriptionstringrequired — documentsarray<RequiredDocument>required — all_uploadedbooleanrequired — can_generate_resolutionbooleanrequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant
NoticeNotFoundResponse - 409The notice has no confirmed employer yet — confirm the entity first
RequiredDocumentsEntityUnconfirmedResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500The notice read failed (sanitized body)
one of (RequiredDocumentsServerErrorResponse · AccountSetupIncompleteResponse)
POST/api/notices/{id}/required-documentsUpload checklist document
Upload a document for a checklist requirement. multipart/form-data with `file` + `document_key`. Payroll spreadsheets (CSV/XLSX) trigger the 4-path side-effect pipeline: PATH A rate verification, PATH B balance/payroll verification, PATH C ADP per-employee SUI, PATH D smart verification + auto-protest letter generation. All side effects are tenant-scoped (KRT-1479 P4b.7 root-cause fix).
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body RequiredDocumentsMultipartBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
file | string | required | Document file binary |
document_key | string | required | Checklist key, e.g., quarterly_sui_wages | quarterly_payroll_summary |
Responses
- 200Uploaded; checklist re-evaluated
RequiredDocumentsPostResponseField Type Required Description okenum: truerequired — document_idstring · uuidrequired — all_uploadedbooleanrequired — path_statusRequiredDocumentsPathStatusrequired — rate_verificationRequiredDocumentsRateVerificationRefusaloptional — payroll_periodRequiredDocumentsPayrollPeriodoptional — idempotent_replayenum: trueoptional — - 400Validation error / missing file or document_key
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant
NoticeNotFoundResponse - 429Rate limit exceeded (uploads use the 10/min ceiling)
NoticeRateLimitResponse - 500Server error / storage upload / document insert failed
one of (SanitizedServerErrorWithErrorCode · AccountSetupIncompleteResponse)
POST/api/notices/{id}/no-responseClose a notice as No response needed
The CPA's one click: record the verdict 'No response needed' and close an open SUI rate notice whose rate checked inside the range. A standalone (self-tenant) employer's founding member may click it too.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Verdict recorded; the notice is closed
NoticeNoResponsePostResponseField Type Required Description okenum: truerequired — verdictenum: "no_response"required — - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — not a CPA-class session nor the self-tenant founder, or the database refused the close (code NO_RESPONSE_FORBIDDEN)
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant (malformed, unknown or foreign id)
NoticeNotFoundResponse - 409Not offered: not an open, in-range SUI rate notice
NoticeNoResponseRefusal - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — sanitized (`SanitizedServerError`), or the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/noticesList notices
List notices for the caller's tenant with filters / sort / pagination. Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
status | query | string | optional | — |
filter | query | enum: "all" | "closes_this_week" | "closing_soon" | "open" | … | optional | — |
state | query | string | optional | — |
search | query | array<string> | optional | Free-text search across notice fields and the employer name. Repeat the key to AND terms (at most 4); more answers 400. |
date_from | query | string | optional | — |
date_to | query | string | optional | — |
sort_by | query | enum: "deadline" | "created_at" | "penalty_amount" | "notice_type" | … | optional | — |
sort_dir | query | enum: "asc" | "desc" | optional | — |
page | query | integer | optional | — |
page_size | query | integer | optional | — |
limit | query | integer | optional | — |
ids | query | array<string · uuid> | optional | Narrow the list to these notice ids (comma-separated, at most 100 distinct UUIDs). Over the cap or a malformed id answers 400. |
notice_type | query | string | optional | — |
entity_id | query | string · uuid | optional | — |
include_counts | query | enum: "1" | optional | — |
all_businesses | query | enum: "1" | optional | — |
Responses
- 200Notices
NoticesListResponseField Type Required Description noticesarray<NoticeListItem>required — totalintegerrequired — pageintegerrequired — page_sizeintegerrequired — countsNoticeQueueCountsoptional — entity_scopeobjectoptional — - 400Validation error — a query parameter outside its vocabulary (status, filter, ids, search count, date_from/date_to). The body names the parameter.
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — `entity_id` names a business outside the caller's own wall (FORBIDDEN_ENTITY; same answer for a sibling, a foreign tenant's id and an id that exists nowhere).
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/notices/historical-finesHistorical fines analysis
Aggregated historical fines analysis with statute-of-limitations for the caller's tenant. Returns total fines, recoverable amounts, expiring counts, and per-state breakdown.
notices.readResponses
- 200Fines analysis
HistoricalFinesResponseField Type Required Description totalFinesnumberrequired — noticeCountintegerrequired — recoverableAmountnumberrequired — recoverableCountintegerrequired — expiringCountintegerrequired — expiredAmountnumberrequired — topAmountsarray<object>required — dateRangeobjectrequired — byStateobjectrequired — estimatedRecoveryobjectrequired — historicalReviewPurchasedbooleanrequired — - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/notices/review-queueCPA review queue
CPA review queue ordered by deadline urgency. Notices in pipeline states NEEDS_REVIEW / READY_FOR_REVIEW / AWAITING_PAYROLL / VERIFIED. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.read`. Each row carries `draft_approve_refusal`: the approve door's letter verdict on the row's live draft — `no_draft`, `internal_memo`, an unfilable-letter reason (e.g. `unfilled_placeholders`, `untracked_template`), or null when the draft is an approvable letter.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | optional | — |
offset | query | integer | optional | — |
financial_impact | query | string | optional | — |
Responses
- 200Review queue
ReviewQueueResponseField Type Required Description noticesarray<object>required — countintegerrequired — totalintegerrequired — cappedbooleanrequired — - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA access required
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/actionCPA action on notice
CPA action on a reviewed notice (approve, dismiss, request revision). Approve records the approval and moves no pipeline_status; the notice becomes ACTIONED only when it is filed. dismissed→DISMISSED, revision_requested→NEEDS_REVIEW. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body NoticeActionBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
action | enum: "approved" | "dismissed" | "revision_requested" | required | — |
notes | string | optional | — |
Responses
- 200Action recorded
NoticeActionResponseField Type Required Description okenum: truerequired — pipeline_statusstringrequired — audit_event_pendingenum: trueoptional — warningstringoptional — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — approval access required; or the writer refused a payroll override this caller may not make (`PAYROLL_OVERRIDE_FORBIDDEN`), or an approval with no interactive session behind it (`HUMAN_APPROVAL_REQUIRED`), or an approve from a login that cannot read the letter (`LETTER_UNREADABLE`).
NoticeActionForbiddenResponse - 404Notice not found — existence-opaque: a foreign tenant, a sibling entity and an unknown id are one answer. `code: NOTICE_NOT_FOUND` only when the lifecycle writer itself lost the row.
NoticeActionNotFoundResponse - 409Refused — nothing was written. A gate held (payroll, reconciliation, the head letter, the entity), a concurrent decision won the optimistic lock (no `code`), the notice is not actionable (no `code`), or the lifecycle writer refused the move (its own code, rendered verbatim).
NoticeActionConflictResponse - 422The lifecycle writer refused: the head letter still has unfilled placeholders (`DRAFT_HAS_PLACEHOLDERS`, rendered verbatim).
NoticeActionUnprocessableResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse) - 503The draft-state read did not come back — a transient, not a gate holding. Nothing was changed; retry.
NoticeActionUnavailableResponse
GET/api/notices/{id}/eventsList notice pipeline events
Pipeline event audit trail for a notice.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Events
NoticeEventsListResponseField Type Required Description eventsarray<NoticeEvent>required — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Notice not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/eventsCreate notice pipeline event
Record a manual pipeline event on a notice (e.g. evidence_verified).
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body NoticeEventCreateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
event_type | enum: "evidence_verified" | required | — |
description | string | optional | — |
Responses
- 201Event recorded
NoticeEventCreateResponseField Type Required Description eventNoticeEventrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Notice not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/generate-draftGenerate draft response
Manually trigger draft response generation for a notice. Cookie sessions go through the Stripe gate; Bearer keys bypass. Bearer scope: `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body GenerateDraftRequest
| Field | Type | Required | Description |
|---|---|---|---|
response_type | enum: "protest" | "optimize" | "pay_now" | "urgent" | … | optional | — |
cpa_decision_reason | string | optional | Required (10+ characters) when `response_type` is `protest` on a paper unemployment claim: the CPA's own reason for protesting, recorded in the audit trail. |
Responses
- 200Draft generated
GenerateDraftResponseField Type Required Description okenum: truerequired — draft_idstring · uuidrequired — draft_lengthintegerrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 402Subscription required (cookie-session callers only)
MiscUnauthorizedResponse - 409Generation produced a BLOCKED draft (missing payroll, a redaction placeholder, or the manual-review placeholder rather than a letter). The row exists and is named by `draft_id`, but it is not a letter and cannot be approved or filed until it is regenerated. Or (`TRACKED_NOT_CHECKED`): the notice is tracked, not checked (X1), so no one-click draft is written. Write the response by hand instead (PATCH). Nothing is written. Or (`FACTS_UNCONFIRMED`): the CPA has not confirmed what Kreto read (employer, notice type, deadline) — POST /api/notices/{id}/confirm-facts first. Asked after the not-found wall, X1 and the employer refusal. Nothing is written. `CLAIM_ANSWERED_THROUGH_SIDES` (Q19): the paper claim is linked to a SIDES request, which is the answer of record — no draft is generated.
one of (NoticeDraftClaimAnsweredThroughSidesResponse · GenerateDraftBlockedResponse · NoticeDraftTrackedNotCheckedResponse · NoticeDraftFactsUnconfirmedResponse) - 422Generation refused as a human gate, not a fault: the draft is waiting on a fact only a person can supply (`needs_input.kind`; today the employer's legal name, added on the record `entity_id` names), or the output guard refused a letter citing evidence this notice does not have (`unbacked_evidence_claim`) or claiming timeliness on a passed or unknown deadline (`untimely_protest_claim`). Also `CLAIM_PROTEST_REASON_REQUIRED`: a `protest` asked for on a paper unemployment claim without the CPA's `cpa_decision_reason` (owner rule N1 — Kreto never recommends a protest on a claim).
NoticeDraftGenerationRefusal - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/processTrigger notice pipeline
Manually trigger the notice pipeline (re-classify, re-cross-reference, regenerate draft). CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`. Cookie sessions go through the Stripe gate.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Pipeline complete
ProcessNoticeResponseField Type Required Description okenum: truerequired — pipelineobjectrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 402Subscription required (cookie-session callers only)
MiscUnauthorizedResponse - 403Forbidden — CPA access required
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (NoticeProcessServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/process-with-payrollProcess notice with payroll
Queues the validate+draft pipeline (a PROCESS_PAYROLL_VERIFICATION job) after payroll has been uploaded (only valid from AWAITING_PAYROLL). `deduplicated` is true when a verification job already open for the notice absorbed the request. Cookie sessions go through the Stripe gate. Bearer scope: `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 202Verification job queued
ProcessWithPayrollResponseField Type Required Description okenum: truerequired — jobIdstringrequired — deduplicatedbooleanrequired — - 400Validation error / wrong pipeline state / no payroll
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 402Subscription required (cookie-session callers only)
MiscUnauthorizedResponse - 404Notice not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/confirm-factsConfirm what Kreto read
Record the CPA's confirmation of what Kreto read on the notice — the employer, the notice type and the deadline, each with its source — and write one hash-chained audit row. The draft doors answer 409 FACTS_UNCONFIRMED until this is recorded; a later change to any of the three re-opens it. CPA-only.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body NoticeConfirmFactsBody
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
notice_type | string | required | — |
deadline | string | required | — |
ein_tail_pick | enum: "notice" | "profile" | "neither" | optional | Required while the notice's FEIN and the employer's EIN on file disagree unanswered: `notice` (the notice is right; the letter prints it), `profile` (the profile is right; the notice's reading is corrected) or `neither` (hold — the facts are not confirmed). |
Responses
- 200Confirmation recorded — or, for `ein_tail_pick: neither`, the hold recorded (`held: true`; the facts stay unconfirmed)
NoticeConfirmFactsResponse - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — not a CPA caller, or missing scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant
NoticeNotFoundResponse - 409A fact cannot be confirmed yet (employer unconfirmed, no type, no deadline), the notice's FEIN and the employer's EIN on file disagree with no pick (EIN_TAIL_PICK_REQUIRED), or a fact changed since the panel was opened (FACTS_CHANGED).
NoticeConfirmFactsRefusal - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/notices/{id}/payroll-comparisonPayroll vs notice rate comparison
The SUI-rate notice's payroll comparison, read from its one stored cross-reference result (the same figure, state and recommendation as GET /cross-reference; never recomputed). Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Comparison
PayrollComparisonResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Notice not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/draft/advanceAdvance draft through approval chain
Advance the draft response through approval steps (reviewed → counter_signed → submitted). CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body DraftAdvanceBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
step | enum: "reviewed" | "counter_signed" | "submitted" | required | — |
notes | string | optional | — |
Responses
- 200Draft advanced
DraftAdvanceResponseField Type Required Description successenum: truerequired — currentStepenum: "reviewed"required — unchangedenum: trueoptional — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA access required
MiscUnauthorizedResponse - 404Notice not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/draft-feedbackSubmit draft feedback
Submit thumbs-up/thumbs-down feedback on a generated draft response. Used to train future draft generation. Bearer keys gate via `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body DraftFeedbackBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
draft_id | string · uuid | required | — |
rating | enum: "thumbs_up" | "thumbs_down" | required | — |
comment | string | optional | — |
Responses
- 200Feedback recorded
DraftFeedbackResponseField Type Required Description successenum: truerequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Notice or draft not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/confirm-sui-rateConfirm/override SUI rate
CPA confirms or overrides the ADP-sourced SUI rate. `confirm` marks the ADP rate as verified; `override` stores a CPA-corrected rate alongside the ADP original; `assign` sets the notice's own assigned rate (`assigned_rate` in the declared `rate_unit`) without a rate record; `previous` stores the CPA-entered previous (last year's) rate (`previous_rate` in the declared `rate_unit`), labelled CPA-entered, and re-runs the figure. `confirm`/`override`/`assign` re-queue a draft parked on the rate and clear that wait. Bearer keys gate via `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body ConfirmSuiRateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
action | enum: "confirm" | "override" | "assign" | "previous" | required | — |
override_rate | number | optional | — |
previous_rate | number | optional | — |
assigned_rate | number | optional | — |
rate_unit | enum: "percent_points" | "fraction" | optional | — |
Responses
- 200Rate confirmed/overridden
ConfirmSuiRateResponse - 400Validation error / cannot determine state
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Notice or SUI rate record not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/confirm-tax-yearConfirm tax year
CPA states the tax year a notice does not print. Stored on the notice as a stated fact with source `cpa_confirmed`, which the payroll request, the document checklist and the AWAITING_PAYROLL gate then read. Refused (409) when the notice already states its year. Cookie sessions require a CPA role; Bearer keys gate via `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body ConfirmTaxYearBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
tax_year | integer | required | — |
Responses
- 200Tax year confirmed
ConfirmTaxYearResponseField Type Required Description successenum: truerequired — tax_yearintegerrequired — sourceenum: "cpa_confirmed"required — confirmed_bystringrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Cookie session without a CPA role
MiscUnauthorizedResponse - 404Notice not found
MiscNotFoundResponse - 409The notice already states its tax year, or the write matched no row
MiscValidationErrorResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/fileMark notice filed
Mark a notice as filed with the agency (KRT-1476 Feature 1 AC5). Captures confirmation_number + filed_at. Auto two-step transition open|needs_review → in_progress → filed. Optimistic lock prevents double-file races. SESSION-ONLY, CPA-class roles: recording a filing is a human attestation, so a Bearer key is refused with 403 SESSION_REQUIRED even when it carries `notices.write` (parity with the APPROVAL_REQUIRED v1 `resolutions.execute`). Requires the notice to be APPROVED (`cpa_action = 'approved'`) and not already filed; otherwise 409 APPROVAL_REQUIRED with no writes.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body NoticeFileBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
confirmation_number | string | required | — |
notes | string | optional | — |
Responses
- 200Notice filed, or the confirmation number recorded on a notice already filed without one (`confirmation_backfilled: true`)
NoticeFileResponse - 202Owner rule E2: a CPA firm's client recorded an employer STATEMENT (`filing.employer_reported`); nothing is filed and the notice stays open until the CPA confirms
NoticeFileEmployerReportResponseField Type Required Description statusenum: "employer_reported"required — reported_atstringrequired — messagestringrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA access required, or SESSION_REQUIRED for a Bearer caller
MiscUnauthorizedResponse - 404Notice not found
MiscNotFoundResponse - 409Already filed / closed / not yet approved (APPROVAL_REQUIRED) / race lost / E2: EMPLOYER_FILING_REPORT_UNAVAILABLE (the employer's report cannot be recorded yet; nothing was saved)
one of (NoticeFileConflictResponse · NoticeFileEmployerReportUnavailableResponse) - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/verify-rateVerify SUI rate
Verify a notice's assigned SUI rate against the employer's payroll experience. If payroll data is incomplete, returns needsData=true + the list of required fields. No input is ever defaulted: each figure is taken from the body, else the latest payroll upload, else the figures printed on the notice. When years in business, the stated tax year, or an experience figure the state's method reads (benefit charges, contributions, average annual payroll, employee count) is not stated, the answer is the named refusal (`resultState: "pending_data"`, `missing_inputs`). Cookie sessions go through the Stripe gate. Bearer scope: `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body VerifyRateBody
| Field | Type | Required | Description |
|---|---|---|---|
totalBenefitCharges | number | optional | — |
totalContributions | number | optional | — |
totalTaxableWages | number | optional | — |
averageAnnualPayroll | number | optional | — |
yearsInBusiness | integer | optional | — |
employeeCount | integer | optional | — |
voluntaryContributions | number | optional | — |
Responses
- 200Rate verification result, OR the named refusal (`resultState: "pending_data"` + `missing_inputs`) when an input the verdict reads is not stated, OR needsData prompt
one of (VerifyRateResultResponse · VerifyRatePendingDataResponse · VerifyRateNeedsDataResponse) - 400Validation error / state not supported
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 402Subscription required (cookie-session callers only)
MiscUnauthorizedResponse - 404Notice not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithDetails · AccountSetupIncompleteResponse)
POST/api/notices/uploadUpload notice
Single-notice intake. Auto-matches/creates entity from extracted text, uploads to storage, creates notice + document records, fires the pipeline asynchronously. Cookie sessions go through Stripe gate + rate-limit + idempotency middleware. SESSION-ONLY: an API key is refused (401 naming the session plane; 403 SESSION_REQUIRED from the handler's second wall) even when it carries `notices.write`. There is no API, MCP or CLI upload today: it is unpublished until it takes a file through the job queue with an approval step (ruling G4-Q6).
Request body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
file | string · binary | required | — |
entity_id | string · uuid | optional | — |
parent_notice_id | string · uuid | optional | — |
entity_attribution_source | string | optional | Caller's attribution claim. Only the exact value "user_selected" is honoured (it relaxes the classifier's authority over the employer match); any other value is ignored, never rejected. |
Responses
- 200Uploaded, or the duplicate receipt when the same bytes are already on file (`duplicate: true`, with the placement disclosure)
NoticeUploadResponse - 400Validation error (`error_code: invalid_body`) — missing/oversized form, bad `entity_id` / `parent_notice_id` format, absent file
MiscUploadClassifiedErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 402Subscription required (cookie-session callers only)
MiscUnauthorizedResponse - 403Forbidden — cookie caller's role is not permitted, or SESSION_REQUIRED (`reason: session_required`) for any non-session caller (owner-inbox item 170)
MiscUnauthorizedResponse - 404`entity_id` or `parent_notice_id` is not visible to this tenant (absent, soft-deleted and cross-tenant share one body)
MiscNotFoundResponse - 409Duplicate notice (`error_code: duplicate`) — same tenant, same SHA-256 content hash
MiscUploadClassifiedErrorResponse - 413File too large (`error_code: file_too_large`) — over MAX_UPLOAD_BYTES
MiscUploadClassifiedErrorResponse - 415Unsupported media type (`error_code: unsupported_mime`) — the declared mime is outside ALLOWED_MIME
MiscUploadClassifiedErrorResponse - 422Received but unreadable (`error_code: parse_failed`) — empty, corrupt or password-protected bytes; no notice row was created
MiscUploadClassifiedErrorResponse - 429Rate limit exceeded
MiscRateLimitRefusalResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse) - 502Storage upload failed (`error_code: storage_upload_failed`) — the notice row was rolled back
MiscUploadClassifiedErrorResponse
GET/api/notices/{id}/cross-referenceCross-reference notice with payroll
Cross-reference notice against payroll data; runs SUI-specific analysis with state-by-state taxable wage bases + max rates when applicable. Returns needs_payroll_data:true when payroll data is missing for the relevant period (auto-falls-back to adjacent years). Persists cross_reference_analysis on the notice. Bearer keys gate via `notices.read` AND `payroll.read` — it reads payroll rows, and `hasScope()` is exact membership, so a key holding only `notices.read` is refused. SWEEP C16 corrected the declaration, which named notices.read alone.
notices.readpayroll.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Result OR needs_payroll_data prompt
one of (CrossReferenceResultResponse · CrossReferenceNeedsDataResponse · CrossReferenceNoFormulaResponse · object) - 401Unauthorized
MiscUnauthorizedResponse - 404Notice not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/send-for-signatureSend notice response for signature
E-signature flow (KRT-1476 Feature 1 Phase 6): renders/uploads a response PDF if no document_id is provided, creates a signature_requests row with secure token + 7-day expiry, sends the signing-link email to the client. Returns signing_link + request_id even if email delivery fails (best-effort). CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body SendForSignatureBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
signer_name | string | required | — |
signer_email | string · email | required | — |
signer_title | string | optional | — |
expires_days | integer | optional | — |
document_id | string · uuid | optional | — |
document_title | string | optional | — |
Responses
- 201Signature request created
SendForSignatureResponseField Type Required Description okenum: truerequired — signing_linkstringrequired — request_idstring · uuidrequired — secure_tokenstringrequired — expires_atstring · date-timerequired — document_idstring · uuidrequired — email_sentbooleanrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA access required
MiscUnauthorizedResponse - 404Notice or document not found
MiscNotFoundResponse - 409Notice already filed/closed, document linked elsewhere, a signing request already live, a reused document that is a government form with no mapped signature line (owner ruling S2), or — owner rule E1 — the notice's letter has not been approved by a CPA (`reason: letter_not_approved`, `code: LETTER_NOT_APPROVED_FOR_SIGNING`).
MiscValidationErrorResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/signature-requests/{id}/cancelCancel a signing request
Withdraw one PENDING, unexpired e-signature request. The signing link stops working at once, and the notice's signature-requested fact is re-derived in the same call. Firm plane only: CPA-class cookie roles, or a Bearer key with `notices.write` and an unrestricted entity scope. Unknown, foreign and out-of-scope ids share one 404 body.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Signing request (signature_requests) UUID |
Responses
- 200Request cancelled
CancelSigningRequestResponseField Type Required Description okenum: truerequired — request_idstringrequired — statusenum: "cancelled"required — notice_idstringrequired — signature_requested_atstringrequired The notice's re-derived signature-requested fact. null = no request holds it any more, so the draft can be regenerated or revised. - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA-class access required
MiscUnauthorizedResponse - 404Signing request not found (or not the caller's)
CancelSigningRequestNotFound - 409Not cancellable — signed, expired or already cancelled
CancelSigningRequestRefusal - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error — including: cancelled, but the notice's signature fact was not re-derived
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/signature-requests/{id}/resendResend a signing link
Replace a signing link with a new one and email it to the signer. The previous link stops working at once. A live request keeps its expiry; a lapsed one (pending past expiry, or expired) is renewed with a new window and returns to pending (E4). Each request may be emailed at most 5 times in all. Cookie session only, CPA-class roles. Unknown and foreign ids share one 404 body.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Signing request (signature_requests) UUID |
Responses
- 200Link rotated; email attempted
ResendSigningRequestResponseField Type Required Description okenum: truerequired — request_idstringrequired — email_sentbooleanrequired — expires_atstringrequired A LIVE request keeps its expiry. A LAPSED one (E4) gets a new window equal to its original lifetime, clamped to 1–30 days. signing_linkstringoptional Present ONLY when the email could not be sent: the new link, shown this once so the firm can deliver it another way. Never stored or shown again. - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA-class access required
MiscUnauthorizedResponse - 404Signing request not found (or not the caller's)
ResendSigningRequestNotFound - 409Not resendable — signed, expired, cancelled, document withdrawn, or changed concurrently
ResendSigningRequestRefusal - 429This request has been emailed the maximum number of times
ResendSigningRequestLimit - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse) - 503App origin not configured, or resend is unavailable on this deployment
ResendSigningRequestUnavailable
GET/api/signature/request/{token}View a signing link
The signing page's view of a LIVE signing link: title, the employer it is for, signer, expiry and an app link to the document. Every closed link answers one 404 body.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
token | path | string | required | The signing link's secret token |
Responses
- 200The live request
SigningRequestViewField Type Required Description document_titlestringrequired — employer_namestringrequired E3: the employer the document is for (its display name), or null when the request is tied to no live employer. document_urlstringrequired — signer_namestringrequired — signer_titlestringrequired — expires_atstringrequired — statusstringrequired — signature_placementSignaturePlacementrequired S2: where the signature goes — the same plan the stamp follows. `form_line` boxes are PDF points (origin bottom-left) on the form's own signature line; `appended_page` (letters and uploads) means no overlay on the document; `not_signable` is an unmapped form. Null when it could not be read (the sign door still decides). sign_actionenum: "response" | "authorization" | "document"required What signing does for this document — a protest response letter (`response`), a government authorization form (`authorization`), or any other document. The page's button words come from it. - 404Closed — the one body for every closed or unknown link
SigningLinkClosed - 500Server error
SanitizedServerError
POST/api/signature/request/{token}Request a new signing link, or send / verify the signer's code
Request a new link (E4). When the token is a real request whose link has lapsed, the person who sent it is emailed (at most once a day); nothing is ever re-issued to the holder. One 200 body for every token. With `?code=send|verify` (owner ruling S1) the same door emails or checks the signer's one-time code; a closed or unknown link answers the GET's one 404 body, and a wrong, expired, absent or locked code answers one 422 body.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
token | path | string | required | The signing link's secret token |
code | query | enum: "send" | "verify" | optional | S1 one-time code: `send` emails a 6-digit code to the request's signer address (only while customer sends are on); `verify` checks the typed code. Absent → the renewal ask. |
Request body SignerCodeVerifyBody
| Field | Type | Required | Description |
|---|---|---|---|
code | string | required | The 6-digit code |
Responses
- 200Renewal ask: the one answer for every token. `?code=send`: the code was emailed, or `sent: false` when customer sends are off (nothing stored). `?code=verify`: verified, with the time signing must happen by.
one of (SigningRenewalAsked · SignerCodeSent · SignerCodeNotSent · SignerCodeVerified) - 400`?code=verify` with an unparseable body
SigningLinkClosed - 404`?code=` on a closed or unknown link — GET's one 404 body
SigningLinkClosed - 422`?code=verify`: the code is wrong, expired, absent or locked
SigningLinkClosed - 429Too many requests for this link or from this address, or too many codes sent for this link
SigningLinkClosed - 500Server error
SanitizedServerError - 503`?code=`: signing is not available on this deployment yet (the code store's migration is not applied) — nothing sent or verified.
object
POST/api/notifications/alerts/{id}/acknowledgeAcknowledge an isolation alert
Release a tenant-scope isolation failure bell on an open notice. The body is optional; when sent it must be `{ raisers_rev }`. Bells with an automatic release are refused (409 `ALERT_HAS_AUTOMATIC_RELEASE`); a bell that changed since it was read is refused (409 `ALERT_CHANGED`). Cookie session only, CPA-class roles. Malformed, unknown and foreign ids share one 404 body.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Operational alert (notice_operational_alerts) UUID. A malformed id answers the same 404 as an unknown one. |
Request body AcknowledgeAlertBody
| Field | Type | Required | Description |
|---|---|---|---|
raisers_rev | integer | required | The `raisers_rev` the operator saw on the bell (the feed's hint). If the bell moved since, the answer is 409 `ALERT_CHANGED`. |
Responses
- 200Released
AcknowledgeAlertResponseField Type Required Description acknowledgedenum: truerequired — remainingintegerrequired Other raisers still holding the bell up after this release (0 = the bell is resolved). - 400A body was sent and it is not JSON or not `{ raisers_rev }`
AcknowledgeAlertBadRequest - 401Unauthorized — no session, or a Bearer key was presented
MiscUnauthorizedResponse - 403Forbidden — CPA-class access required
MiscUnauthorizedResponse - 404Alert not found (or not the caller's)
AcknowledgeAlertNotFound - 409The bell changed since it was read, or it has an automatic release
AcknowledgeAlertConflict - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/confirm-deadlineSet a missing deadline
CPA sets a deadline the notice does not state (N2). Only an EMPTY clock is set; a clock that already holds a date is refused (409) and never overwritten here. A date in the past or over a year ahead needs `confirm_unusual: true` (422 otherwise). The entry is audited as `deadline_confirmed` with who entered it. Cookie sessions require a CPA role, or a `client_user` session of a self-serve employer workspace (no CPA on the roster); a CPA firm's client is refused 403 `CPA_SETS_DEADLINE` after the 404. Bearer keys gate via `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body ConfirmDeadlineBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
clock | enum: "filing" | "protest" | required | Which clock to set: the filing due date or the protest date |
date | string | required | — |
confirm_unusual | enum: true | optional | Required to save a date in the past or more than a year ahead (422 otherwise) |
Responses
- 200Deadline set
ConfirmDeadlineResponseField Type Required Description successenum: truerequired — clockenum: "filing" | "protest"required — datestringrequired — provenanceenum: "entered_by_hand"required — entered_bystringrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Cookie session without a CPA role, or a CPA firm's client (CPA_SETS_DEADLINE — the CPA sets the deadline)
MiscUnauthorizedResponse - 404Notice not found
MiscNotFoundResponse - 409That clock already holds a date, or the write matched no row (CLOCK_ALREADY_SET)
MiscValidationErrorResponse - 422The date is in the past or over a year ahead and was not confirmed (DEADLINE_NEEDS_CONFIRM)
MiscValidationErrorResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/employer-filing-report/rejectReject an employer's filing report
Owner rule E2 — decline the employer's standing report that they filed the response, with a required reason the employer reads; they may report again. Writes one `filing.employer_report_rejected` audit event; the notice is unchanged. Cookie session only, CPA-class roles. Malformed, unknown, foreign and out-of-scope ids share one 404 body; the 400 and 409 refusals come only after it.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | Notice UUID. A malformed id answers the same 404 as an unknown one. |
Request body RejectEmployerFilingReportBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | required | Why the CPA could not confirm — the employer reads it. |
Responses
- 200Rejected — the employer sees the reason
RejectEmployerFilingReportResponseField Type Required Description stateenum: "rejected"required — reasonstringrequired — event_idstringrequired — - 400The reason is missing or too long
RejectEmployerFilingReportError - 401Unauthorized — no session, or a Bearer key was presented
MiscUnauthorizedResponse - 403Forbidden — CPA-class access required
MiscUnauthorizedResponse - 404Notice not found (or not the caller's)
RejectEmployerFilingReportNotFound - 409No employer filing report is waiting for confirmation
RejectEmployerFilingReportConflict - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/agency-replyRecord agency reply
CPA records the agency's reply to a FILED notice (Q6): type + date (+ an optional document of this notice). Writes `agency_response_type` + `agency_response_date` and one chained `notice_agency_reply_recorded` audit row; never the pipeline status. The same reply again replays (200, nothing written). Session only — API keys are refused (403 SESSION_REQUIRED); cookie sessions require a CPA role, or the standalone (self-tenant) employer's founding member.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body AgencyReplyBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
replyType | enum: "acceptance" | "rejection" | "counter" | "additional_info_request" | required | — |
receivedOn | string | required | — |
documentId | string · uuid | optional | One of THIS notice's live documents holding the reply |
Responses
- 200Reply recorded, or the same reply replayed
AgencyReplyResponseField Type Required Description outcomeenum: "recorded" | "replayed"required — replyTypeenum: "acceptance" | "rejection" | "counter" | "additional_info_request"required — receivedOnstringrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403API key (SESSION_REQUIRED), a cookie session that is neither a CPA role nor the self-tenant founder, or the database refused the write (code AGENCY_REPLY_FORBIDDEN)
MiscUnauthorizedResponse - 404Notice not found (absent, foreign, out of scope), or DOCUMENT_NOT_FOUND
MiscNotFoundResponse - 409NOT_FILED, REPLY_ALREADY_RECORDED (an acceptance/rejection is final) or WRITE_REFUSED (the notice changed mid-write)
MiscValidationErrorResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/splitSplit a document into notices by pages
CPA cuts a document flagged as possibly holding more than one notice into one child notice per page range (I-7, ruling Q23). Each child is linked to the parent and re-read through the pipeline; the parent is closed as split. Cookie sessions require a CPA role; Bearer keys gate via `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body SplitNoticeBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
ranges | string | required | One notice per comma-separated page range (1-based, no overlap) |
Responses
- 200Split into separate notices
SplitNoticeResponseField Type Required Description successenum: truerequired — resolutionenum: "split"required — child_notice_idsarray<string>required — messagestringrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Cookie session without a CPA role
MiscUnauthorizedResponse - 404Notice not found — unknown, malformed, foreign, soft-deleted or out-of-scope id (one body)
MiscNotFoundResponse - 409Not flagged (MULTI_NOTICE_NOT_FLAGGED), already split or confirmed (MULTI_NOTICE_ALREADY_RESOLVED), or no stored document (SPLIT_NO_DOCUMENT)
MiscValidationErrorResponse - 422The page ranges do not fit the document (SPLIT_RANGES_INVALID), ask for more notices than one split may make (SPLIT_TOO_MANY_RANGES), or the stored PDF cannot be opened (SPLIT_PDF_UNREADABLE)
MiscValidationErrorResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/{id}/confirm-singleConfirm a flagged document is one notice
CPA confirms a document flagged as possibly holding more than one notice is ONE notice (I-7, ruling Q23); its deadline is trusted again. Cookie sessions require a CPA role; Bearer keys gate via `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200Confirmed as one notice
ConfirmSingleNoticeResponseField Type Required Description successenum: truerequired — resolutionenum: "single"required — - 401Unauthorized
MiscUnauthorizedResponse - 403Cookie session without a CPA role
MiscUnauthorizedResponse - 404Notice not found — unknown, malformed, foreign, soft-deleted or out-of-scope id (one body)
MiscNotFoundResponse - 409Not flagged (MULTI_NOTICE_NOT_FLAGGED) or already split or confirmed (MULTI_NOTICE_ALREADY_RESOLVED)
MiscValidationErrorResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/notices/{id}/decision-cardGet the notice decision card
The notice decision card: four rows (Read · Checked · Proposed · Decision), each with a state, labelled figures and a link to the page that holds the evidence. Read-only. An employer caller (client_user session or an employer-minted key) gets the employer projection. An unknown, foreign-tenant, sibling-employer or deleted notice answers the same 404.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200The decision card
NoticeDecisionCardField Type Required Description noticeIdstring · uuidrequired — planeenum: "cpa" | "employer"required — currentenum: "read" | "checked" | "proposed" | "decision"required — figureHeldbooleanrequired Employer plane: Kreto's computed figures are held until the CPA approves. rowsobjectrequired — - 400Validation error
NoticeValidationErrorResponse - 401Unauthorized
NoticeUnauthorizedResponse - 403Forbidden — missing required scope
NoticeUnauthorizedResponse - 404Notice not found in caller's tenant or employer scope
NoticeNotFoundResponse - 429Rate limit exceeded
NoticeRateLimitResponse - 500Server error — sanitized (`SanitizedServerError`), or the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/notices/{id}/experience-figuresRead the experience-data request
The notice's open experience-data request (the figures asked, the tax year and the state) and whether the caller may answer it (the employer's owner). `request: null` when none is open. Bearer scope: `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200The open request (or null) and the caller's answer right
ExperienceFiguresGetResponseField Type Required Description requestExperienceRequestrequired — can_answerbooleanrequired — - 401Unauthorized
MiscUnauthorizedResponse - 404Notice not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithDetails · AccountSetupIncompleteResponse)
POST/api/notices/{id}/experience-figuresAnswer the experience-data request
The employer owner states the experience figures the open request asks for. They are stored as employer-stated notice facts (never as a Kreto-verified figure) and the rate decision re-runs through the job queue. Only the asked keys are accepted; `average_annual_payroll` must be above 0. A replay of the same answer returns 200 `deduplicated: true`; a different answer to an answered request is 409 `request_not_open`. Bearer scope: `notices.write` on an entity-scoped employer key.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Request body ExperienceFiguresBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
request_id | string | required | — |
figures | object | required | — |
Responses
- 200Answered; the re-run is queued
ExperienceFiguresAcceptedField Type Required Description answeredenum: truerequired — request_idstringrequired — deduplicatedbooleanoptional — rerunobjectoptional — - 400`figure_invalid` — a figure is missing, unknown or out of range
one of (ExperienceFiguresError · MiscValidationErrorResponse) - 401Unauthorized
MiscUnauthorizedResponse - 403`ENTITY_OWNER_REQUIRED` — only the employer's owner answers
ExperienceFiguresError - 404Notice not found
MiscNotFoundResponse - 409`request_not_open` — no open request with that id
ExperienceFiguresError - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithDetails · AccountSetupIncompleteResponse)
POST/api/notices/{id}/experience-figures/requestAsk the employer for experience figures
The CPA asks the employer's owner for the experience-statement figures that check an in-range SUI rate increase Kreto could only check against the state's range. Opens the same experience-data request Kreto fires on its own. Idempotent: an open request answers `deduplicated: true` and nothing is re-sent. 409 `figures_on_file` / `not_owed` / `nothing_to_ask` / `no_employer_owner` (the honest reason). Session only, CPA roles.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Notice UUID |
Responses
- 200The open request (new, or deduplicated)
ExperienceAskResponseField Type Required Description requestedenum: truerequired — deduplicatedbooleanrequired — requestExperienceRequestrequired — - 401Unauthorized
MiscUnauthorizedResponse - 403Not a CPA role
ExperienceFiguresError - 404Notice not found
MiscNotFoundResponse - 409The ask cannot be opened (the code names why)
ExperienceFiguresError - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithDetails · AccountSetupIncompleteResponse)
POST/api/notices/bulk/actionBulk approve, dismiss or request revision on notices
Apply one CPA action to up to 200 notices. Per-id: tenant assert, no-op guard, one hash-chained audit row. If the tenant-scoped existence probe fails, or a cross-tenant row is detected post-fetch, the batch answers 500 and NOTHING is written.
notices.writeRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
action | enum: "approved" | "dismissed" | "revision_requested" | required | — |
ids | array<string · uuid> | required | Target ids, 1..200. Duplicates are collapsed before any write. |
notes | string | optional | — |
Responses
- 200Per-id ledger. Partial success is a 200.
NoticesBulkActionResultField Type Required Description appliedintegerrequired Ids that produced a real state change (and therefore exactly one audit row each). skippedarray<object>required Ids deliberately not mutated. Present on a 200 — partial success is the contract, not an error. failedarray<object>required Ids whose mutation raised. `probe_failed` means the tenant-scoped existence probe itself errored, so nothing was attempted. unauditedarray<object>optional A SUBSET of `applied`: ids that really moved but are missing a hash-chained audit entry. Absent on the normal path. untimelinedarray<object>optional A SUBSET of `applied`: ids whose decision and audit entry committed but whose timeline (notice_events) row failed to write. Absent on the normal path. - 400Body failed validation (ids outside 1..200, or a bad uuid).
- 403Authenticated but not CPA-class (`FORBIDDEN_ROLE`), or the Bearer key lacks the declared scope.
- 422At least one id does not resolve in this tenant (`unresolved_ids`). The whole batch is refused and nothing is written.
- 500A tenant-scope leak was detected post-fetch (defense-in-depth third wall). No writes occurred.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/notices/bulk/exportExport notices as CSV
Export notices either by explicit id list OR by the same narrowing the /notices queue applies — exactly one of `ids` / `query`. A `query` that resolves to more than 1000 rows is REFUSED 413 with the true size, never truncated into a file that looks complete. `amount` is emitted human-formatted (the column is dollars, not the stored cents).
notices.readRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | optional | Target ids, 1..1000. Over the cap the request is REFUSED, never silently truncated. |
query | object | optional | — |
Responses
- 200CSV attachment: `text/csv; charset=utf-8` with `Content-Disposition: attachment; filename="notices-export-<YYYY-MM-DD>.csv"`. Header row, in order: `notice_id`, `employer`, `agency`, `state`, `type`, `amount`, `deadline`, `status`, `status_code`, `confirmation_number`, `filed_at`, `outcome`, `received`.
string - 400Body failed validation — `ids` outside 1..1000, or a value that is not a UUID.
- 403Authenticated but out of scope — a `client_user` asking outside its entity memberships, or a Bearer key that does not carry the declared scope.
- 413`EXPORT_TOO_LARGE` — the `query` matches more than 1000 notices. The body names the true `total` and the `limit`; nothing is written.
- 500The read failed, or a post-fetch tenant-scope leak was detected (defense-in-depth third wall). No partial file is written.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Ops1
GET/api/admin/notice-intake/unmatchedUnmatched inbound email queue (ops only)
OPS ONLY: a staff door for Kreto operators, not a customer API. Lists the 50 most recent inbound emails that matched no firm. It has no page by design. Requires a system administrator session.
Responses
- 200The most recent unmatched inbound emails (max 50).
UnmatchedIntakeResponseField Type Required Description unmatchedarray<UnmatchedIntakeRecord>required — - 401No admin session (`requireAdminAPI`).
- 403The session is not a system administrator, or must complete an MFA step-up (`requireAdminAPI`).
- 500Unexpected failure: the sanitized body `captureApiError` writes. An admin session with no `user_profiles` row answers `requireAdminAPI`'s `AccountSetupIncompleteResponse` instead.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Packets4
GET/api/packets/{id}Get response packet
Get response packet with status + document list + linked notice metadata (KRT-224).
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Response packet UUID |
Responses
- 200Packet
PacketDetailResponseField Type Required Description okenum: truerequired — packetPacketDetailrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Packet not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/packets/{id}/downloadDownload response packet
Generate 7-day signed download URLs for all packet documents.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Response packet UUID |
Responses
- 200Signed URLs
PacketDownloadResponseField Type Required Description okenum: truerequired — packet_idstring · uuidrequired — documentsarray<PacketDownloadDocument>required — expires_in_secondsintegerrequired — messagestringrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Packet or documents not found
MiscNotFoundResponse - 409Packet still assembling, or its assembly failed (code PACKET_FAILED) — only a finished pack (ready / submitted / acknowledged / rejected) is served
MiscValidationErrorResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/packets/{id}/submitSubmit response packet
Mark a response packet as submitted, record submission reference, and transition the linked notice to `filed` status. Refused unless packet is in `ready` state. SESSION-ONLY: this files a notice, which is a human attestation, so a Bearer key is refused with 403 SESSION_REQUIRED even when it carries `notices.write` (parity with POST /api/notices/{id}/file and the APPROVAL_REQUIRED v1 `resolutions.execute`). Also requires the linked notice to be APPROVED (`cpa_action = 'approved'`) unless it is already filed; otherwise 409 APPROVAL_REQUIRED with no writes of any kind — neither the notice nor the packet moves.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Response packet UUID |
Request body PacketSubmitBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
submission_ref | string | required | — |
submission_method | enum: "email" | "fax" | "portal" | "mail" | optional | — |
notes | string | optional | — |
Responses
- 200Submitted
PacketSubmitResponseField Type Required Description okenum: truerequired — packetobjectrequired — messagestringrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — SESSION_REQUIRED: a Bearer key cannot submit (this files a notice); role_forbidden: a session without CPA-level access (e.g. an employer user) cannot submit — refused before any read or write
MiscUnauthorizedResponse - 404Packet not found
MiscNotFoundResponse - 409Packet not in ready status / notice not yet approved (APPROVAL_REQUIRED)
MiscValidationErrorResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/packets/assembleAssemble response packet
Trigger an async packet assembly job. Returns a job_id; poll the packet endpoint for status. Cookie sessions go through rate-limit + idempotency middleware.
notices.writeRequest body PacketAssembleBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
notice_id | string · uuid | required | — |
resolution_id | string · uuid | optional | — |
evidence_ids | array<string · uuid> | optional | — |
Responses
- 200Job queued
PacketAssembleResponseField Type Required Description okenum: truerequired — job_idstring · uuidrequired — notice_idstring · uuidrequired — statusenum: "assembling"required — messagestringrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Notice not found
MiscNotFoundResponse - 409Nothing may be sealed: no approved letter, the letter is the manual-review placeholder, or a filed notice's letter was never approved (rule-pack-placeholder).
PacketAssembleRefusal - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Payroll9
GET/api/payroll/dataGet payroll dropdown data
Returns entities visible to the caller, optionally with notices for a specific entity. Backs the payroll-page entity dropdown.
payroll.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | optional | When present, also returns notices for this entity (tenant-scoped) |
Responses
- 200Entities + (optional) notices
PayrollDataResponseField Type Required Description entitiesarray<PayrollEntity>required — noticesarray<PayrollNoticeSummary>required — payroll_uploadsPayrollUploadHistoryrequired — - 400Validation error
PayrollValidationErrorResponse - 401Unauthorized
PayrollUnauthorizedResponse - 403Forbidden — missing required scope
PayrollUnauthorizedResponse - 429Rate limit exceeded
PayrollRateLimitResponse - 500Entity-scope resolution or the entities read failed (sanitized, with empty lists), or a post-fetch tenant-scope leak on the entities or notices read was refused (`isolation`). No partial data is returned.
one of (PayrollServerErrorResponse · PayrollDataServerErrorResponse · AccountSetupIncompleteResponse)
GET/api/payroll/template.csvDownload payroll CSV template
Returns a CSV template for payroll data upload. Columns match the generic format payroll-validator.ts expects.
payroll.readResponses
- 200CSV template
string - 401Unauthorized
- 403Forbidden — missing required scope
- 429Rate limit exceeded
- 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/payroll/parse-previewPreview payroll file headers
Server-side payroll preview parser. Returns only the header row + row count for client-side column-mapping; the full file still ships to /api/payroll/upload for processing. Routes XLSX through the hardened safeParseXLSX wrapper (xlsx@0.18.5 has known CVEs that the wrapper neutralizes).
payroll.readRequest body PayrollMultipartFileBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
file | string | required | CSV or Excel binary |
Responses
- 200Header preview + row count
PayrollParsePreviewResponseField Type Required Description headersarray<string>required — rowCountintegerrequired — formatenum: "xlsx" | "csv"required — - 400Validation error / invalid file
PayrollValidationErrorResponse - 401Unauthorized
PayrollUnauthorizedResponse - 403Forbidden — missing required scope
PayrollUnauthorizedResponse - 413File too large (>10 MB)
PayrollValidationErrorResponse - 415Unsupported file type
PayrollValidationErrorResponse - 429Rate limit exceeded
PayrollRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/payroll/validateValidate ADP payroll CSV
ADP payroll CSV validator (KRT-396). Accepts multipart with `file` + `notice_id`. Validates the CSV against the ADP-specific validator and persists the result to validation_results. Tenant-scoped on the notice access check + insert.
payroll.writeRequest body PayrollValidateMultipartBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
file | string | required | ADP CSV file |
notice_id | string · uuid | required | Notice UUID to validate against |
Responses
- 200Validation result + persisted record id
PayrollValidateResponseField Type Required Description okenum: truerequired — validation_result_idstring · uuidrequired — validationobjectrequired — - 400Validation error / file too large / not CSV
PayrollValidationErrorResponse - 401Unauthorized
PayrollUnauthorizedResponse - 403A valid Bearer key without `payroll.write`, a session that must complete an MFA step-up (`mfa_required`), or a non-CPA cookie role (`role_forbidden` — refused before any read or write)
PayrollValidateForbiddenResponse - 404Notice not found in caller's tenant
PayrollNotFoundResponse - 429Rate limit exceeded
PayrollRateLimitResponse - 500Entity-scope resolution, the notice read or the validation_results insert failed, or the validator threw — every arm is finalized, so sanitized to the shared server-error body
one of (PayrollServerErrorResponse · AccountSetupIncompleteResponse)
GET/api/payroll/pullGet payroll data status
Returns the current payroll-data status for an entity — cache tier, age in days, and active provider connection (if any). All queries tenant-scoped.
payroll.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | required | Employer entity UUID (required) |
Responses
- 200Status bundle
PayrollPullStatusResponseField Type Required Description connectionobjectrequired — payroll_dataobjectrequired — cache_tierenum: "hot" | "warm" | "cold" | "none"required — age_daysintegerrequired — - 400`entity_id` missing or not a UUID — the validator's refusal, with its issues
PayrollPullStatusValidationErrorResponse - 401Unauthorized
PayrollUnauthorizedResponse - 403A valid Bearer key without `payroll.read` (`Forbidden — missing required scope: payroll.read`), a session that must complete an MFA step-up (`mfa_required`), or an `entity_id` outside a client_user's entities (`Not authorized for this entity`)
PayrollPullStatusForbiddenResponse - 429Rate limit exceeded
PayrollRateLimitResponse - 500Server error — a snapshot/connection read failed or the tenant third wall refused (both finalized, so sanitized)
one of (PayrollServerErrorResponse · AccountSetupIncompleteResponse)
POST/api/payroll/pullTrigger payroll pull (removed — always 410)deprecated
Deprecated. The automated payroll pull this endpoint triggered has been removed. It still authenticates, validates the body and gates the entity (so a malformed or out-of-scope call gets a deterministic 4xx), then answers 410 Gone. Upload payroll with POST /api/payroll/upload instead; GET /api/payroll/pull still serves the cached status.
payroll.writeRequest body PayrollPullPostBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
trigger_type | string | required | — |
trigger_ref | string | optional | — |
force_refresh | boolean | optional | — |
Responses
- 400Validation error
PayrollValidationErrorResponse - 401Unauthorized
PayrollUnauthorizedResponse - 403Forbidden — missing required scope, or the entity is outside a client_user's scope
PayrollUnauthorizedResponse - 410Gone — the payroll pull was removed; returned for every well-formed, in-scope call
PayrollPullGoneResponse - 429Rate limit exceeded
PayrollRateLimitResponse - 500No route-level 500 arm — past auth, body validation and the entity gate the call answers 410. Declared on the payroll family's shared server-error body
one of (PayrollServerErrorResponse · AccountSetupIncompleteResponse)
POST/api/payroll/notice-syncADP payroll sync for a notice (removed — always 410)deprecated
Deprecated. The automated ADP payroll pull this endpoint drove has been removed. It still authenticates and validates the body (so a malformed call gets a deterministic 4xx), then answers 410 Gone. Upload payroll with POST /api/payroll/upload instead.
payroll.writeRequest body PayrollNoticeSyncBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
notice_id | string · uuid | required | — |
needed_year | integer | required | — |
needed_quarter | integer | required | — |
username | string | required | — |
password | string | required | — |
company_code | string | optional | — |
save_connection | boolean | optional | — |
Responses
- 400Validation error
PayrollValidationErrorResponse - 401Unauthorized
PayrollUnauthorizedResponse - 403Forbidden — missing required scope
PayrollUnauthorizedResponse - 410Gone — the ADP pull was removed; returned for every well-formed call
PayrollNoticeSyncGoneResponse - 429Rate limit exceeded
PayrollRateLimitResponse - 500No route-level 500 arm — past auth and body validation the call answers 410. Declared on the payroll family's shared server-error body
one of (PayrollServerErrorResponse · AccountSetupIncompleteResponse)
POST/api/payroll/upload-reportUpload payroll report
Upload a payroll-related report file (CSV/PDF/Excel) for an entity. Persists the file to Supabase storage and one `documents` row, nothing else — no parse, no job, no payroll rows. The file then appears in the document library.
payroll.writeRequest body PayrollUploadReportMultipartBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
file | string | required | CSV/PDF/Excel binary (max 50 MB) |
report_type | string | required | Normalized report type (e.g., payroll_journal) |
entity_id | string · uuid | required | — |
Responses
- 200Uploaded — the storage object and the documents row were both persisted; the body names them
PayrollUploadReportResponseField Type Required Description successenum: truerequired — documentIdstring · uuidrequired — content_hashstringrequired — - 400Validation error
PayrollValidationErrorResponse - 401Unauthorized
PayrollUnauthorizedResponse - 403Forbidden — missing required scope
PayrollUnauthorizedResponse - 404Entity not found in caller's tenant
PayrollNotFoundResponse - 429Rate limit exceeded (uploads use the 10/min ceiling)
PayrollRateLimitResponse - 500Entity-scope resolution or the entity read failed, the tenant third wall refused, or the handler threw (the document insert included) — finalized or captureApiError, sanitized either way
one of (PayrollServerErrorResponse · AccountSetupIncompleteResponse) - 502The file could not be written to storage (`storage_upload_failed`, sanitized) — nothing was persisted; retry
StorageUploadFailedResponse
POST/api/payroll/uploadUpload payroll file (heavy)
Upload a payroll CSV or Excel file for an entity, optionally linked to a notice. Pipeline: storage upload → payroll_uploads + payroll_records insert → SUI detail extraction (per-employee/month/state from Excel) → mergePayrollData into entity_payroll_data → validation_results when notice-linked → auto-trigger verification pipeline for AWAITING_PAYROLL notices. Idempotency-aware: same x-idempotency-key replays the prior response.
payroll.writeRequest body PayrollUploadFormBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
file | string | required | CSV or Excel binary (max 10 MB) |
entity_id | string · uuid | required | — |
notice_id | string · uuid | optional | — |
column_map | string | optional | JSON-stringified ColumnMap (employee_name, gross_wages, state, etc.) |
Responses
- 200Uploaded (validation summary + row counts), or the duplicate receipt when the same file is already on file (`duplicate: true`)
PayrollUploadResponse - 400Validation error / file too large / unsupported file type / unsafe XLSX
PayrollValidationErrorResponse - 401Unauthorized
PayrollUnauthorizedResponse - 402Subscription required (cookie-session callers only)
PayrollUnauthorizedResponse - 403Forbidden — missing required scope
PayrollUnauthorizedResponse - 404Entity or notice not found in caller's tenant
PayrollNotFoundResponse - 422Refused — nothing was saved. `payroll_identities_unresolved`: some employees could not be matched to the roster. `payroll_name_collision` (owner ruling 2026-10-04 item 3): two name-only lines with the same name, date and state state different SUI rates — a COUNT and a DATE, never a name. `payroll_period_implausible` (walk restyle cause 5): lines state a pay period outside the plausible range — a COUNT and one example period. Also `parse_failed` / `LEGACY_XLS_FORMAT`.
PayrollUploadRefusalResponse - 429Rate limit exceeded (uploads use the 10/min ceiling)
PayrollRateLimitResponse - 500Entity-scope resolution, the entity or notice read, or the snapshot tenant third wall failed, or the pipeline threw — finalized or captureApiError, sanitized either way
one of (PayrollServerErrorResponse · AccountSetupIncompleteResponse) - 502The file could not be written to storage (`storage_upload_failed`, sanitized) — no upload row was written; retry
StorageUploadFailedResponse
POA7
GET/api/poa-authorizationsList POA authorizations
List POA authorizations for the caller's tenant (KRT-464). Filter by employer_entity_id / poa_entity_id. Revoked rows are included by default; `include_revoked=false` narrows to non-revoked rows. Each row carries `status`: revoked | rejected | expired | active | draft. Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
employer_entity_id | query | string · uuid | optional | — |
poa_entity_id | query | string · uuid | optional | — |
include_revoked | query | boolean | optional | — |
Responses
- 200POA authorizations
POAAuthorizationsListResponseField Type Required Description authorizationsarray<POAAuthorization>required — - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/poa-authorizationsRequest POA authorization
Request a new POA authorization (KRT-464). 409 if an authorization already exists for the same employer + poa + scope. Bearer keys gate via `notices.write`.
notices.writeRequest body POAAuthorizationCreateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
employer_entity_id | string · uuid | required | — |
poa_entity_id | string · uuid | required | — |
scope | array<POAScope> | required | — |
Responses
- 201Authorization requested
POAAuthorizationCreateResponseField Type Required Description authorizationPOAAuthorizationrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403A valid Bearer key without `notices.write`, a session that must complete an MFA step-up (`mfa_required`), or `role_forbidden` — a non-CPA cookie role, refused before any read or write
NoticesWriteForbiddenResponse - 409Authorization already exists
MiscValidationErrorResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/poa-authorizations/{id}/generateGenerate pre-filled POA PDF
Generate a pre-filled POA PDF for a previously-created authorization. Two modes: `download` (returns the PDF + filing instructions only) and `send-for-signature` (creates a signature_request and emails the signer + a copy to the originating CPA). Bearer keys gate via `notices.write`. Kreto does NOT submit the form to the agency — the user files manually using the agency portal or mailing address in the `filing` block of the response.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | POA authorization UUID |
Request body POAGenerateBody
| Field | Type | Required | Description |
|---|---|---|---|
template_id | string | required | Pre-auth template id (e.g., 'poa-tax-representation'). Browse via GET /api/pre-authorization-templates. |
mode | enum: "download" | "send-for-signature" | optional | 'download' returns the PDF only. 'send-for-signature' creates a signature_request, emails the signer + originator. Default: send-for-signature. |
signer_name | string | optional | — |
signer_email | string · email | optional | — |
overrides | object | optional | — |
Responses
- 200Mode=download: pre-filled PDF + filing instructions
objectField Type Required Description modeenum: "download"required — authorization_idstring · uuidrequired — template_idstringrequired — template_namestringrequired — document_hashstringrequired — filingobjectrequired — missing_required_fieldsarray<string>required — - 201Mode=send-for-signature: signing request created + email queued
objectField Type Required Description modeenum: "send-for-signature"required — authorization_idstring · uuidrequired — signature_request_idstring · uuidrequired — secure_tokenstringrequired — signing_linkstringrequired — expires_atstring · date-timerequired — document_hashstringrequired — template_idstringrequired — template_namestringrequired — filingobjectrequired — missing_required_fieldsarray<string>required — signer_emailstring · emailrequired — originator_emailedbooleanrequired — - 400Validation error (missing template_id, invalid body, etc.), or `code: signer_name_conflict` — the request names two different signers: `signer_name` and a signer-name field in `overrides` (employer_signatory_name / signatory_name / authorized_rep_name) disagree. One authorization has one signer of record; send one name, or the same name in both.
one of (POAGenerateSignerConflictResponse · MiscValidationErrorResponse) - 401Unauthorized
MiscUnauthorizedResponse - 404Authorization or employer entity not found in your tenant
MiscNotFoundResponse - 409Authorization is revoked — cannot generate a document; or (send mode) the form has no signature line mapped: "This form has no signature line mapped yet — Kreto cannot stamp it." (owner ruling S2 — no state POA form is mapped today, so send mode is refused before anything is generated; download mode is unchanged).
MiscValidationErrorResponse - 422Refused: a required template field has no value. No PDF is built — a placeholder is never printed into a legal document. `reason: entity_incomplete` = fix the employer record; `reason: needs_input` = supply the field in `overrides` and retry. OR `resultState: "pending_data"` + `missing_inputs: ["firm_name"]` when the template prints the firm on a required line and the firm's record states no name (never a stand-in label).
one of (POAGenerateFieldsMissingResponse · POAGenerateFirmNameMissingResponse) - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error (PDF generation failure, DB error)
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PATCH/api/poa-authorizations/{id}Revoke POA authorization
Revoke a POA authorization. Bearer keys gate via `notices.write`. 409 if already revoked.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | POA authorization UUID |
Responses
- 200Authorization revoked
POAAuthorizationRevokeResponseField Type Required Description authorizationPOAAuthorizationrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403A valid Bearer key without `notices.write`, a session that must complete an MFA step-up (`mfa_required`), or `role_forbidden` — a non-CPA cookie role, refused before any read, or a revoking UPDATE the database refused on a row the caller can read
NoticesWriteForbiddenResponse - 404Authorization not found
MiscNotFoundResponse - 409Already revoked (`already_revoked`) — including when a concurrent revoke won the race (the conditional UPDATE matched no active row and the re-read shows it revoked)
POAAuthorizationRevokeConflictResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/pre-authorization-templatesList pre-auth templates
List pre-authorization templates from the global Kreto library. No tenant data is read. Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
category | query | string | optional | — |
state | query | string | optional | — |
Responses
- 200Templates
PreAuthTemplatesListResponseField Type Required Description templatesarray<PreAuthTemplateSummary>required — totalintegerrequired — - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/pre-authorization-templates/{id}Get pre-auth template
Retrieve full template details (content template + field definitions + signature fields). Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | Pre-auth template ID |
Responses
- 200Template
PreAuthTemplateDetailResponseField Type Required Description templatePreAuthTemplateFullrequired — - 401Unauthorized
MiscUnauthorizedResponse - 404Template not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/pre-authorization-templates/{id}/autofillAuto-fill pre-auth template
Auto-fill a pre-authorization template from entity data. Optionally renders to PDF and uploads to storage when generate_pdf=true (and employer_id is provided). CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | required | Pre-auth template ID |
Request body PreAuthAutofillBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_data | object | required | — |
overrides | object | optional | — |
generate_pdf | boolean | optional | — |
employer_id | string · uuid | optional | — |
cpa_name | string | optional | — |
cpa_firm | string | optional | — |
Responses
- 200Auto-fill result (with optional pdf_hash)
PreAuthAutofillResponseField Type Required Description rendered_contentstringrequired — resolved_fieldsobjectrequired — missing_required_fieldsarray<string>required — missing_optional_fieldsarray<string>required — pdf_hashstringoptional — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA access required
MiscUnauthorizedResponse - 404Template not found
MiscNotFoundResponse - 422Refused (generate_pdf=true only): a required template field has no value. No PDF is built or uploaded — a placeholder is never printed into a legal document. `reason: entity_incomplete` = fix the employer record / entity_data; `reason: needs_input` = supply the field in `overrides` and retry.
POAGenerateFieldsMissingResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Reports7
GET/api/reports/notice-summaryNotice summary report
Group notices by status + (primary) type for the caller's tenant. Bearer keys gate via `notices.read`.
notices.readResponses
- 200Summary
ReportsNoticeSummaryResponseField Type Required Description by_statusobjectrequired — by_typeobjectrequired — totalintegerrequired — - 401Unauthorized
ReportsUnauthorizedResponse - 429Rate limit exceeded
ReportsRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/reports/roiROI report
ROI metrics: total savings, notices resolved, average resolution days, protests won, and ROI multiple. Bearer keys gate via `notices.read`.
notices.readResponses
- 200ROI
ReportsRoiResponseField Type Required Description total_savingsnumberrequired — notices_resolvedintegerrequired — avg_resolution_daysnumberrequired — resolution_sample_sizeintegerrequired — protests_wonintegerrequired — roi_multiplenumberrequired — - 401Unauthorized
ReportsUnauthorizedResponse - 429Rate limit exceeded
ReportsRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/reports/sui-impactSUI impact report
SUI-related notice analysis with per-notice breakdown. Bearer keys gate via `notices.read`.
notices.readResponses
- 200SUI impact
ReportsSuiImpactResponseField Type Required Description summaryobjectrequired — noticesarray<ReportsSuiImpactNotice>required — - 401Unauthorized
ReportsUnauthorizedResponse - 429Rate limit exceeded
ReportsRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/reports/compliance-scorecardCompliance scorecard
Entity-level compliance scores (the compliance monitor's) with a nullable trend (no manufactured signal). Bearer keys gate via `notices.read`.
notices.readResponses
- 200Scorecard
ReportsScorecardResponseField Type Required Description entitiesarray<ReportsScorecardEntity>required — - 401Unauthorized
ReportsUnauthorizedResponse - 429Rate limit exceeded
ReportsRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/reports/state-authorizationState authorization report
POA status per entity per state, joining poa_authorizations + documents tables. Bearer keys gate via `notices.read`.
notices.readResponses
- 200State authorization
ReportsStateAuthResponseField Type Required Description entitiesarray<ReportsStateAuthEntity>required — - 401Unauthorized
ReportsUnauthorizedResponse - 429Rate limit exceeded
ReportsRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/reports/response-timeResponse-time report
Notice processing-time metrics (classification seconds, resolution days, draft hours) with monthly breakdown. Bearer keys gate via `notices.read`.
notices.readResponses
- 200Response-time metrics
ReportsResponseTimeResponseField Type Required Description avg_classification_secondsintegerrequired — classification_sample_sizeintegerrequired — avg_resolution_daysnumberrequired — resolution_sample_sizeintegerrequired — avg_draft_hoursnumberrequired — draft_sample_sizeintegerrequired — data_errorsobjectrequired — total_processedintegerrequired — by_montharray<ReportsResponseTimeMonth>required — - 401Unauthorized
ReportsUnauthorizedResponse - 429Rate limit exceeded
ReportsRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/reports/generateGenerate report
Generate a compliance report for the requested entities + date range. Returns JSON or a CSV file (Content-Disposition: attachment). Up to 50 entities per call. Bearer keys gate via `audit.write` (full tenant scope) — KRT-1479 P5 review RATIFIED this as a compute-heavy mutation so read-only keys cannot burn compute (route.ts docblock); SWEEP C16 corrected the declaration, which still said `audit.read`. Cookie sessions use RLS-enforced entity access.
audit.writeRequest body ReportsGenerateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
type | string | required | — |
entityIds | array<string · uuid> | required | — |
dateRange | object | required | — |
format | enum: "json" | "csv" | optional | — |
Responses
- 200Report (JSON when format=json; CSV download otherwise)
ReportsGenerateJsonResponseField Type Required Description typestringrequired — rowsarray<object>required — summaryobjectoptional — csvstringoptional — - 400Validation error
ReportsValidationErrorResponse - 401Unauthorized
ReportsUnauthorizedResponse - 403Access denied for one or more entityIds
ReportsUnauthorizedResponse - 429Rate limit exceeded
ReportsRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Resolutions4
GET/api/resolutions/{id}Get resolution draft
Returns the draft + tenant status counts.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Resolution draft UUID |
Responses
- 200Draft + stats
ResolutionGetResponseField Type Required Description okenum: truerequired — draftResolutionDraftRecordrequired — statsobjectrequired — - 400Validation error
ResolutionValidationErrorResponse - 401Unauthorized
ResolutionUnauthorizedResponse - 403Forbidden — missing required scope
ResolutionUnauthorizedResponse - 404Draft not found in caller's tenant
ResolutionNotFoundResponse - 429Rate limit exceeded
ResolutionRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PATCH/api/resolutions/{id}Edit resolution draft (CPA)
CPA edits draft text. Only allowed in `draft` or `in_review` status.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Resolution draft UUID |
Request body ResolutionPatchBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
cpa_edits | string | required | — |
status | enum: "in_review" | optional | — |
Responses
- 200Draft updated
ResolutionPatchResponseField Type Required Description okenum: truerequired — draft_idstring · uuidrequired — statusstringrequired — - 400Validation error / draft in non-editable status
ResolutionValidationErrorResponse - 401Unauthorized
ResolutionUnauthorizedResponse - 403Forbidden — CPA access required
ResolutionUnauthorizedResponse - 404Draft not found in caller's tenant
ResolutionNotFoundResponse - 429Rate limit exceeded
ResolutionRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/resolutions/{id}/approveApprove resolution draft (CPA)
CPA approves a resolution draft. Status transitions in_review → approved. Only valid from in_review.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Resolution draft UUID |
Request body ResolutionApproveBody
| Field | Type | Required | Description |
|---|---|---|---|
notes | string | optional | — |
Responses
- 200Approved
ResolutionApproveResponseField Type Required Description okenum: truerequired — draft_idstring · uuidrequired — statusenum: "approved"required — audit_event_pendingenum: trueoptional — - 400Invalid draft id, malformed JSON (`INVALID_REQUEST_BODY`), a schema-invalid body (`details`), or a draft not in draft/in_review.
ResolutionApproveBadRequestResponse - 401Unauthorized
ResolutionUnauthorizedResponse - 402An active subscription is required for this action.
ResolutionApprovePaymentRequiredResponse - 403Forbidden — not an interactive session (`SESSION_REQUIRED`), not a CPA-firm role, a payroll override this caller may not make, no human behind the approval, or a login that cannot read the letter (`LETTER_UNREADABLE`).
ResolutionApproveForbiddenResponse - 404Draft (or its notice) not found in the caller's tenant — existence-opaque: a foreign tenant's id is the same answer.
ResolutionApproveNotFoundResponse - 409Refused — nothing was approved. The draft is blocked, payroll is unverified or its state unreadable, the letterhead is incomplete, the notice bridge refused, or the lifecycle writer refused the move (its own code, rendered verbatim). `draft_superseded`: the draft was replaced by a newer version (one live draft per notice) — approve `live_draft_id` instead.
one of (object · ResolutionApproveConflictResponse) - 422The letter still has unfilled placeholders (`DRAFT_HAS_PLACEHOLDERS`).
ResolutionApproveUnprocessableResponse - 429Rate limit exceeded
ResolutionRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/resolutions/{id}/rejectReject resolution draft (CPA)
CPA rejects a draft (draft|in_review → rejected).
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Resolution draft UUID |
Request body ResolutionRejectBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
rejection_reason | string | required | — |
Responses
- 200Rejected
ResolutionRejectResponseField Type Required Description okenum: truerequired — draft_idstring · uuidrequired — statusenum: "rejected"required — - 400Validation error / wrong status
ResolutionValidationErrorResponse - 401Unauthorized
ResolutionUnauthorizedResponse - 403Forbidden — CPA access required
ResolutionUnauthorizedResponse - 404Draft not found in caller's tenant
ResolutionNotFoundResponse - 429Rate limit exceeded
ResolutionRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Settings3
GET/api/settings/firmGet firm settings
Firm/tenant settings (name, slug). Bearer keys gate via `admin.read`.
admin.readResponses
- 200Settings
SettingsFirmResponseField Type Required Description namestringrequired — slugstringrequired — created_atstring · date-timerequired — - 401Unauthorized
MiscUnauthorizedResponse - 404Tenant not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PATCH/api/settings/firmUpdate firm settings
Update firm/tenant settings (currently: name only). Bearer keys gate via `admin.write` — the route ENFORCES admin.write (route.ts), and `hasScope()` is exact membership, so a key holding only `admin.read` is refused. SWEEP C16: the declaration said `admin.read` and was the half that was wrong; a PATCH is a write.
admin.writeRequest body SettingsFirmUpdateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
name | string | required | — |
Responses
- 200Settings updated
SettingsFirmResponseField Type Required Description namestringrequired — slugstringrequired — created_atstring · date-timerequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/settings/email-intake/client-addressesList client forwarding addresses
Each client's notice-forwarding address, paged (25 per page) and searchable by legal name. CPA-class cookie session only. `entity_id` narrows to one client; an unknown or foreign id answers an empty list.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
page | query | string | optional | Zero-based page |
q | query | string | optional | Legal-name search |
entity_id | query | string · uuid | optional | — |
Responses
- 200Addresses (or forwarding not switched on)
ClientForwardingAddressesResponseField Type Required Description forwarding_onbooleanrequired False when email forwarding is not switched on here; `clients` is then empty. clientsarray<ClientForwardingAddress>required — pageintegerrequired — page_sizeintegerrequired — totalintegerrequired — - 400Malformed entity_id
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Not a CPA-class role
ClientForwardingAddressesForbidden - 429Rate limit exceeded
MiscRateLimitResponse - 500Internal error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
SIDES12
GET/api/sides/enrollmentsList SIDES enrollments
List all SIDES employer enrollments for the caller's tenant. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.read`.
notices.readResponses
- 200Enrollments
SidesEnrollmentsListResponseField Type Required Description enrollmentsarray<SidesEnrollment>required — - 401Unauthorized
SidesUnauthorizedResponse - 403Forbidden — CPA access required
SidesUnauthorizedResponse - 429Rate limit exceeded
SidesRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/sides/enrollmentsEnroll employer in SIDES
Enroll an employer in SIDES for a specific state. NASWA admin-site enrollment is tracked locally; status starts as `pending`. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`.
notices.writeRequest body SidesEnrollmentCreateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
state_code | string | required | — |
employer_account_number | string | optional | — |
Responses
- 201Enrollment created
SidesEnrollmentCreateResponseField Type Required Description enrollmentSidesEnrollmentrequired — - 400Validation error
SidesValidationErrorResponse - 401Unauthorized
SidesUnauthorizedResponse - 403Forbidden — CPA access required
SidesUnauthorizedResponse - 404Entity not found
SidesNotFoundResponse - 409Already enrolled in this state
SidesValidationErrorResponse - 429Rate limit exceeded
SidesRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/sides/exchangesList SIDES exchanges
List inbound SIDES exchanges for the caller's tenant. Supports status / exchange_type / state_code filters, pagination, and sort. Any authenticated caller can read. Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
status | query | string | optional | — |
exchange_type | query | string | optional | — |
state_code | query | string | optional | — |
sort | query | enum: "due_date" | "received_date" | "status" | optional | — |
limit | query | integer | optional | — |
offset | query | integer | optional | — |
Responses
- 200Exchanges + total
SidesExchangesListResponseField Type Required Description exchangesarray<SidesExchangeListItem>required — totalintegerrequired — limitintegerrequired — offsetintegerrequired — intakeEnabledbooleanrequired Compat name for intake.connected. intakeSidesIntakeStatusrequired — - 400Validation error
SidesValidationErrorResponse - 401Unauthorized
SidesUnauthorizedResponse - 429Rate limit exceeded
SidesRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/sides/exchanges/{id}Get SIDES exchange
Fetch a single SIDES exchange + audit log. Any authenticated caller can read; an employer caller (a `client_user` cookie session or a client_user-minted Bearer key) gets a PII-reduced view: raw XML, request facts, routing ids and claimant first/last names are absent and `exchange.claimant_name` reads 'Name withheld'. The CPA plane keeps the names and `claimant_name` carries the joined name (or 'Unknown claimant'). Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | SIDES exchange / claim UUID |
Responses
- 200Exchange + audit log
SidesExchangeDetailResponseField Type Required Description exchangeobjectrequired — audit_logarray<SidesAuditEntry>required — - 401Unauthorized
SidesUnauthorizedResponse - 404Exchange not found
SidesNotFoundResponse - 429Rate limit exceeded
SidesRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PUT/api/sides/exchanges/{id}Save SI draft response
Save a drafted SI response for an exchange. Validates via Zod and serializes to XML when valid; otherwise stashes the JSON body for round-trip editing. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | SIDES exchange / claim UUID |
Request body SidesSaveDraftBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
response | object | required | — |
Responses
- 200Draft saved
SidesSaveDraftResponseField Type Required Description okenum: truerequired — validbooleanrequired — errorsarray<object>required — - 400Validation error / unsupported exchange type
SidesValidationErrorResponse - 401Unauthorized
SidesUnauthorizedResponse - 403Forbidden — CPA access required
SidesUnauthorizedResponse - 404Exchange not found
SidesNotFoundResponse - 429Rate limit exceeded
SidesRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/sides/exchanges/{id}/submitSubmit SI response (CPA)
Approve and submit an SI response to the SIDES Central Broker. The server injects claimantSSNLast4 from the original request XML, validates, serializes, and posts via the NASWA Model Connector CLI. Re-submit is rejected (409) once an exchange reaches responding/responded/acknowledged. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | SIDES exchange / claim UUID |
Request body SidesSubmitBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
response | object | required | — |
Responses
- 200Submitted
SidesSubmitResponseField Type Required Description okenum: truerequired — statusenum: "responded" | "delivery_unverified"required — response_filestringrequired — - 400Validation error / unsupported exchange type
SidesValidationErrorResponse - 401Unauthorized
SidesUnauthorizedResponse - 403Forbidden — CPA access required
SidesUnauthorizedResponse - 404Exchange not found
SidesNotFoundResponse - 409Exchange already submitted/acknowledged — re-submit blocked
SidesValidationErrorResponse - 422Zod validation failed (with broker-rule remediation)
SidesValidationErrorResponse - 429Rate limit exceeded
SidesRateLimitResponse - 500Server error (e.g., missing SSN in raw_request_xml)
one of (SanitizedServerErrorWithCode · AccountSetupIncompleteResponse) - 502Broker rejected (with business-rule errors)
SidesValidationErrorResponse
GET/api/sides/transmissionsList SIDES transmissions
SIDES transmission audit log. Filter by direction (inbound/outbound) and transmission_status. CPA-class roles only on cookie sessions; Bearer keys gate via `audit.read`.
audit.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
direction | query | enum: "inbound" | "outbound" | optional | — |
status | query | string | optional | — |
limit | query | integer | optional | — |
offset | query | integer | optional | — |
Responses
- 200Transmissions
SidesTransmissionsListResponseField Type Required Description transmissionsarray<SidesTransmission>required — totalintegerrequired — - 401Unauthorized
SidesUnauthorizedResponse - 403Forbidden — CPA access required
SidesUnauthorizedResponse - 429Rate limit exceeded
SidesRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/sides/claimsList SIDES claims (legacy)
List SIDES claims (legacy /sides_separation_requests path). Filter by pending / auto_filled / in_review / overdue / submitted. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
filter | query | enum: "all" | "pending" | "auto_filled" | "in_review" | … | optional | — |
limit | query | integer | optional | — |
offset | query | integer | optional | — |
today | query | string | optional | — |
Responses
- 200Claims
SidesClaimsListResponseField Type Required Description claimsarray<SidesClaimListItem>required — totalintegerrequired — - 400Unknown `filter` value, or a malformed / impossible `today` date
SidesUnauthorizedResponse - 401Unauthorized
SidesUnauthorizedResponse - 403Forbidden — CPA access required
SidesUnauthorizedResponse - 429Rate limit exceeded
SidesRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/sides/claims/{id}Get SIDES claim (legacy)
SIDES claim detail (legacy path) with related transmissions + linked notice. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | SIDES exchange / claim UUID |
Responses
- 200Claim + transmissions + notice
SidesClaimDetailResponseField Type Required Description claimobjectrequired — transmissionsarray<SidesTransmission>required — transmissions_unavailable_reasonenum: "no_linked_notice"optional — noticeobjectrequired — - 401Unauthorized
SidesUnauthorizedResponse - 403Forbidden — CPA access required
SidesUnauthorizedResponse - 404Claim not found
SidesNotFoundResponse - 429Rate limit exceeded
SidesRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/sides/claims/{id}/respondSave SIDES claim draft (legacy)
Save a draft response on the legacy claims path. The `approve` action is permanently blocked (410) — the legacy table lacks the FEIN/state-account/SSN data required for a valid SIDES response. Use the v2 sides_exchanges path instead. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`. Cookie sessions go through the Stripe subscription gate.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | SIDES exchange / claim UUID |
Request body SidesClaimRespondBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
action | enum: "save" | "approve" | required | — |
separation_reason | string | optional | — |
last_day_worked | string | optional | — |
final_wages_cents | integer | optional | — |
discharge_reason | string | optional | — |
voluntary_quit_reason | string | optional | — |
return_to_work_offered | boolean | optional | — |
notes | string | optional | — |
Responses
- 200Draft saved
SidesClaimRespondResponseField Type Required Description successenum: truerequired — response_statusstringrequired — actionstringrequired — - 400Validation error
SidesValidationErrorResponse - 401Unauthorized
SidesUnauthorizedResponse - 402Subscription required (cookie-session callers only)
SidesUnauthorizedResponse - 403Forbidden — CPA access required
SidesUnauthorizedResponse - 404Claim not found
SidesNotFoundResponse - 410Approve is permanently blocked on the legacy path
SidesValidationErrorResponse - 429Rate limit exceeded
SidesRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/sides/claims/bulk/assignBulk assign SIDES separation requests
Assign up to 200 SIDES separation requests to one in-tenant user, or clear the assignee with `null`. The assignee is validated in-tenant first.
notices.writeRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | required | Target ids, 1..200. Duplicates are collapsed before any write. |
assignee | string · uuid | required | — |
Responses
- 200Per-id ledger. Partial success is a 200.
BulkActionResultField Type Required Description appliedintegerrequired Ids that produced a real state change (and therefore exactly one audit row each). skippedarray<object>required Ids deliberately not mutated. Present on a 200 — partial success is the contract, not an error. failedarray<object>required Ids whose mutation raised. `probe_failed` means the tenant-scoped existence probe itself errored, so nothing was attempted. - 400Body failed validation (ids outside 1..200, or a bad uuid).
- 403Authenticated but not CPA-class (`FORBIDDEN_ROLE`), or the Bearer key lacks the declared scope.
- 500A tenant-scope leak was detected post-fetch (defense-in-depth third wall). No writes occurred.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/sides/claims/bulk/exportExport SIDES claims as CSV
Export up to 1000 SIDES claims by id. Employer FEINs are emitted masked (`***-NNNN`), never in full.
notices.readRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | required | Target ids, 1..1000. Over the cap the request is REFUSED, never silently truncated. |
Responses
- 200CSV attachment: `text/csv; charset=utf-8` with `Content-Disposition: attachment; filename="claims-export-<YYYY-MM-DD>.csv"`. Header row, in order: `claim_id`, `type`, `state`, `employer`, `employer_fein_masked`, `status`, `due`, `confidence`, `created`.
string - 400Body failed validation — `ids` outside 1..1000, or a value that is not a UUID.
- 403Authenticated but out of scope — a `client_user` asking outside its entity memberships, or a Bearer key that does not carry the declared scope.
- 500The read failed, or a post-fetch tenant-scope leak was detected (defense-in-depth third wall). No partial file is written.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
State registrations1
POST/api/state-registrations/bulk/exportExport state registrations as CSV
Export up to 1000 state-registration requirements by id, tenant-scoped and entity-narrowed for a `client_user`.
state_registration.readRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | required | Target ids, 1..1000. Over the cap the request is REFUSED, never silently truncated. |
Responses
- 200CSV attachment: `text/csv; charset=utf-8` with `Content-Disposition: attachment; filename="state-registrations-export-<YYYY-MM-DD>.csv"`. Header row, in order: `state`, `requirement`, `status`, `source`, `effective_date`, `next_renewal`, `submitted_at`, `created`, `updated`.
string - 400Body failed validation — `ids` outside 1..1000, or a value that is not a UUID.
- 403Authenticated but out of scope — a `client_user` asking outside its entity memberships, or a Bearer key that does not carry the declared scope.
- 500The read failed, or a post-fetch tenant-scope leak was detected (defense-in-depth third wall). No partial file is written.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
State Registrations2
POST/api/state-registrations/bulk/assignBulk assign state registrations to a firm user
Assign up to 200 state registrations to one in-tenant user, or clear the assignee with `null`. The assignee is validated in-tenant before any write.
state_registration.writeRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | required | Target ids, 1..200. Duplicates are collapsed before any write. |
assignee | string · uuid | required | — |
Responses
- 200Per-id ledger. Partial success is a 200.
BulkActionResultField Type Required Description appliedintegerrequired Ids that produced a real state change (and therefore exactly one audit row each). skippedarray<object>required Ids deliberately not mutated. Present on a 200 — partial success is the contract, not an error. failedarray<object>required Ids whose mutation raised. `probe_failed` means the tenant-scoped existence probe itself errored, so nothing was attempted. - 400Body failed validation (ids outside 1..200, or a bad uuid).
- 403Authenticated but not CPA-class (`FORBIDDEN_ROLE`), or the Bearer key lacks the declared scope.
- 500A tenant-scope leak was detected post-fetch (defense-in-depth third wall). No writes occurred.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/state-registrations/bulk/transitionBulk transition state-registration status
Move up to 200 state registrations to `newStatus`. A registration already in that status is skipped with `no_change`; `reason` is required and is recorded on each audit row.
state_registration.writeRequest body objectrequired
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | required | Target ids, 1..200. Duplicates are collapsed before any write. |
newStatus | enum: "pending" | "prepared" | "submitted" | "approved" | … | required | — |
reason | string | required | — |
Responses
- 200Per-id ledger. Partial success is a 200.
BulkActionResultField Type Required Description appliedintegerrequired Ids that produced a real state change (and therefore exactly one audit row each). skippedarray<object>required Ids deliberately not mutated. Present on a 200 — partial success is the contract, not an error. failedarray<object>required Ids whose mutation raised. `probe_failed` means the tenant-scoped existence probe itself errored, so nothing was attempted. - 400Body failed validation (ids outside 1..200, or a bad uuid).
- 403Authenticated but not CPA-class (`FORBIDDEN_ROLE`), or the Bearer key lacks the declared scope.
- 500A tenant-scope leak was detected post-fetch (defense-in-depth third wall). No writes occurred.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
States6
GET/api/statesList state agencies
List state agencies with tax rules for a given year (default 2026). Read-only Kreto reference data — no tenant scope.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
year | query | integer | optional | — |
Responses
- 200States
StatesListResponseField Type Required Description dataarray<StateAgency>required — countintegerrequired — - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/states/{code}Get state detail
Single state detail with tax rules, surcharges, notice types, protest rules, POA rules, and statutes. Read-only reference data.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
code | path | string | required | 2-letter state code |
year | query | integer | optional | — |
Responses
- 200State detail
StateDetailResponseField Type Required Description dataStateDetailrequired — - 401Unauthorized
MiscUnauthorizedResponse - 404State not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/states/{code}/noticesList state notice types
List state notice types ranked by financial impact. Read-only reference data.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
code | path | string | required | 2-letter state code |
Responses
- 200Notice types
StateNoticeTypesResponseField Type Required Description state_codestringrequired — state_namestringrequired — dataarray<StateNoticeType>required — countintegerrequired — - 401Unauthorized
MiscUnauthorizedResponse - 404State not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/states/{code}/poaList state POA rules
List POA rules for a state. Read-only reference data.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
code | path | string | required | 2-letter state code |
Responses
- 200POA rules
StatePoaResponseField Type Required Description state_codestringrequired — state_namestringrequired — dataarray<StatePoaRule>required — countintegerrequired — - 401Unauthorized
MiscUnauthorizedResponse - 404State not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/states/compareCompare states
Compare up to 5 states side-by-side (full ruleset for each). Read-only reference data.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
states | query | string | required | — |
year | query | integer | optional | — |
Responses
- 200Comparison data
StateComparisonResponseField Type Required Description dataarray<StateComparison>required — countintegerrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/states/withholdingWithholding reciprocity
Withholding reciprocity calculator. Without query params, returns the full reciprocity map. With work_state + home_state, returns the specific reciprocity decision plus each state's full reciprocity list.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
work_state | query | string | optional | — |
home_state | query | string | optional | — |
Responses
- 200Reciprocity data
StateWithholdingResponseField Type Required Description reciprocityAgreementsobjectoptional — totalStatesWithReciprocityintegeroptional — resultobjectoptional — workStateReciprocityarray<string>optional — homeStateReciprocityarray<string>optional — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
TaxDocuments10
GET/api/tax-documents/{id}/items/{itemId}/downloadDownload a delivered document
The file a client delivered against this checklist item. Without `format`, answers this door's own `?format=file` address (never a storage URL); with `format=file`, streams the bytes of the item's own published `uploaded_file` after the same walls. `portal_uploads` is joined only for filename / size / mime. Cookie session only (no Bearer) and CPA-tier.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Tax document request UUID |
itemId | path | string · uuid | required | Tax document item UUID |
format | query | enum: "file" | optional | — |
Responses
- 200The download address (no format), or the file bytes (format=file)
TaxDocumentItemDownloadResponseField Type Required Description urlstringrequired — file_namestringrequired — file_size_bytesintegerrequired — mime_typestringrequired — delivered_atstringrequired — - 400Invalid id format
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — requires CPA-level access
MiscUnauthorizedResponse - 404Request or item not found
MiscNotFoundResponse - 409No file has been delivered for this item yet
MiscValidationErrorResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/tax-documentsList tax document requests
List tax document requests for caller's tenant. Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | optional | — |
status | query | string | optional | — |
tax_year | query | integer | optional | — |
Responses
- 200Requests
TaxDocumentRequestsListResponseField Type Required Description successenum: truerequired — requestsarray<TaxDocumentRequest>required — countintegerrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/tax-documentsCreate tax document request
Create a tax document collection request with optional AI checklist generation (KRT-322). Cookie sessions go through rate-limit + idempotency middleware. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`.
notices.writeRequest body TaxDocumentRequestCreateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
client_id | string · uuid | optional | — |
tax_year | integer | required | — |
tax_type | enum: "personal" | "business" | required | — |
entity_type | enum: "individual" | "s_corp" | "c_corp" | "partnership" | … | optional | — |
notice_id | string · uuid | optional | — |
due_date | string | optional | — |
generate_checklist | boolean | optional | — |
prior_year_items | array<string> | optional | — |
Responses
- 201Request created
TaxDocumentRequestCreateResponseField Type Required Description successenum: truerequired — requestTaxDocumentRequestrequired — items_countintegerrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA access required
MiscUnauthorizedResponse - 404Entity not found in caller's tenant
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithDetails · AccountSetupIncompleteResponse)
GET/api/tax-documents/{id}Get tax document request
Get tax document request detail with all items.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Tax document request UUID |
Responses
- 200Request + items
TaxDocumentRequestDetailResponseField Type Required Description successenum: truerequired — requestTaxDocumentRequestrequired — itemsarray<TaxDocumentRequestItem>required — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Request not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PATCH/api/tax-documents/{id}/items/{itemId}Update tax document item
Update a tax document request item. CPA-class can update label / description / required; client_user / client can update status / uploaded_file / notes. Auto-completes the parent request when all required items reach verified or waived. Bearer keys gate via `notices.write` (treated as CPA-class).
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Tax document request UUID |
itemId | path | string · uuid | required | Tax document item UUID |
Request body TaxDocumentItemUpdateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
status | enum: "pending" | "uploaded" | "verified" | "rejected" | … | optional | — |
uploaded_file | string | optional | Storage path of a portal upload for THIS item. Must match `tenant/<your tenant_id>/portal/<request_id>/<item_id>/<filename>` exactly — the path the client-portal upload writes; any other value is rejected with 400. The ids are the lowercase UUIDs of the addressed request/item and your own tenant, and `<filename>` is the sanitized basename the portal stores: characters `A-Za-z0-9._-` only, no `..`, whole path ≤1024 chars. The portal upload route is the natural writer — set this only to re-point the item at a file already delivered for it. The field is set-only: it cannot be cleared or nulled via PATCH. |
notes | string | optional | — |
label | string | optional | — |
description | string | optional | — |
required | boolean | optional | — |
Responses
- 200Item updated
TaxDocumentItemUpdateResponseField Type Required Description successenum: truerequired — itemTaxDocumentRequestItemrequired — - 400Validation error / expired request
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Request or item not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithDetails · AccountSetupIncompleteResponse)
DELETE/api/tax-documents/{id}/portal-tokenRevoke a document request's upload link
Revoke the outstanding client upload (portal) link for a tax document request. The emailed link stops resolving immediately. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`. 409 when there is no link to revoke or it was already revoked.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Tax document request UUID |
Responses
- 200Upload link revoked
TaxDocumentPortalTokenRevokeResponseField Type Required Description successenum: truerequired — revoked_atstring · date-timerequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Request not found
MiscNotFoundResponse - 409No outstanding link, or already revoked
MiscValidationErrorResponse - 429Rate limited
MiscRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/tax-documents/{id}/remindersList tax document reminders
List reminders sent for a tax document request + current reminder settings (paused state, custom deadline, due date). Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Tax document request UUID |
Responses
- 200Reminders + settings
TaxDocumentRemindersListResponseField Type Required Description successenum: truerequired — remindersarray<TaxDocumentReminder>required — settingsobjectrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Request not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PATCH/api/tax-documents/{id}/remindersUpdate tax document reminder settings
Update reminder settings (pause/resume, set custom deadline). CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Tax document request UUID |
Request body TaxDocumentRemindersUpdateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
reminders_paused | boolean | optional | — |
custom_deadline | string | optional | — |
Responses
- 200Settings updated
TaxDocumentRemindersUpdateResponseField Type Required Description successenum: truerequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA access required
MiscUnauthorizedResponse - 404Request not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/tax-documents/{id}/reminders/sendSend manual reminder
Send a manual reminder for a specific tax document request (KRT-324). CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`. Refused for completed/expired requests.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Tax document request UUID |
Responses
- 200Reminder sent (or skipped with reason)
TaxDocumentManualReminderResponseField Type Required Description successbooleanoptional — reminder_idstring · uuidrequired — pending_items_countintegerrequired — skippedbooleanoptional — skip_reasonstringoptional — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA access required
MiscUnauthorizedResponse - 404Request not found
MiscNotFoundResponse - 422Cannot send reminder for completed/expired request
MiscValidationErrorResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerErrorWithCode · AccountSetupIncompleteResponse)
POST/api/tax-documents/reminders/bulkBulk-send tax document reminders
Send reminders for the SELECTED documents. Each document id is resolved to its request_id (tenant-scoped) and each distinct request is reminded and audited independently (one audit row per request). Terminal (complete/expired) requests are skipped. CPA-class roles only on cookie sessions; Bearer keys gate via `notices.write`.
notices.writeRequest body TaxDocumentBulkRemindersRequest
| Field | Type | Required | Description |
|---|---|---|---|
ids | array<string · uuid> | required | Selected document ids (1..200) |
Responses
- 200Bulk send result (applied / skipped / failed)
TaxDocumentBulkRemindersResponseField Type Required Description appliedintegerrequired — skippedarray<object>required — failedarray<object>required — - 401Unauthorized
MiscUnauthorizedResponse - 403Forbidden — CPA access required
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
TaxLiens2
GET/api/tax-liensList tax liens
List tax liens for the caller's tenant. Filters: entity_id, status, lien_type. Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | optional | — |
status | query | string | optional | — |
lien_type | query | string | optional | — |
limit | query | integer | optional | — |
offset | query | integer | optional | — |
Responses
- 200Tax liens
TaxLiensListResponseField Type Required Description liensarray<TaxLien>required — totalintegerrequired — - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/tax-liensCreate tax lien
Create a tax lien record. Bearer keys gate via `notices.write`.
notices.writeRequest body TaxLienCreateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
lien_amount | number | required | — |
notice_id | string · uuid | optional | — |
lien_type | string | optional | — |
lien_number | string | optional | — |
filing_date | string | optional | — |
filing_jurisdiction | string | optional | — |
tax_periods | array<string> | optional | — |
tax_type | string | optional | — |
response_deadline | string | optional | — |
release_conditions | string | optional | — |
agency | string | optional | — |
Responses
- 201Tax lien created
TaxLienCreateResponseField Type Required Description lienTaxLienrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Entity not found in caller's tenant
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Team3
DELETE/api/team/members/{userId}Remove a member from the firm
Firm admins only (cookie session; Bearer keys are refused). ONE database transaction (`remove_team_member`) locks the firm's admins, stamps `user_profiles.deleted_at` (the database auth helpers then refuse any token the member still holds), revokes every API key the member created in this tenant and writes one `team_member_removed` audit event; THEN the member is signed out everywhere (ban + credential rotation). If that sign-out fails the member is still removed and `ban_pending` is true. Calling DELETE again on that already-removed member is the retry: it re-runs ONLY the sign-out and answers 200 (`api_keys_revoked` 0) — unless a pending re-invitation for the member is live, which reads the 404. A foreign-tenant or unknown id reads the same 404. Refusals: 409 `cannot_remove_self`, 409 `last_admin` (the firm's only active firm admin), 409 `removal_conflict` (removed concurrently), 403 `target_role_not_mutable` (a platform system admin).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
userId | path | string · uuid | required | — |
Responses
- 200Member removed
RemoveTeamMemberResponseField Type Required Description okenum: truerequired — user_idstring · uuidrequired — removed_atstring · date-timerequired — api_keys_revokedintegerrequired — ban_pendingbooleanrequired — - 401No session
TeamMemberError - 403Caller is not a firm admin, or the target is a platform system admin
TeamMemberError - 404Member not found in your firm (existence-opaque)
TeamMemberError - 409cannot_remove_self | last_admin | removal_conflict — `error` is the sentence to show
TeamMemberError - 500Server error — sanitized (`SanitizedServerError`), or a signed-in session with no profile gets `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/team/invitationsInvite a teammate to the firm
Cookie session only (Bearer keys are refused). Firm admins, or the tenant's founding member, invite an address as `cpa_member` or `staff`. The accept link is NEVER returned (owner ruling 97(a)) — the e-mail is the only channel. Re-inviting a member this firm removed lifts their sign-in ban last; if that fails the row exists and the answer is the named 502 carrying its id (resend it to retry).
Request body CpaTeamInvitationCreateRequestrequired
| Field | Type | Required | Description |
|---|---|---|---|
email | string · email | required | — |
invited_role | enum: "cpa_member" | "staff" | required | — |
Responses
- 201Invitation created (and, unless `email_sent` is false, mailed)
CpaTeamInvitationCreateResponseField Type Required Description invitationobjectrequired — email_sentbooleanrequired — email_failure_reasonenum: "suppressed_non_prod" | "rate_limited" | "rejected" | "unavailable" | …optional — - 400Invalid JSON, invalid email, a role other than cpa_member | staff, or the caller's own email
TeamMemberError - 401No session
TeamMemberError - 403Not a firm admin or the founding member, not a CPA-tier caller, or not a session
TeamMemberError - 409already_member (the email already has firm access) | duplicate_pending (a live invitation exists)
TeamMemberError - 500Server error — sanitized (`SanitizedServerError`), or a signed-in session with no profile gets `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse) - 502invite_unban_failed — the invitation exists but the removed member's sign-in could not be restored; no e-mail was sent. Resend it to retry.
CpaTeamInvitationUnbanFailed - 503invite_schedule_failed — the re-invitation's expiry could not be queued. Nothing was written; retry.
CpaTeamInvitationScheduleFailed
POST/api/team/invitations/{id}Resend a pending invitation
Firm admins only (cookie session; Bearer keys are refused). Mints a NEW accept token — the previously mailed link stops working — and re-sends the e-mail. No URL or token is returned. Capped per client IP (429). A foreign-tenant or unknown id reads the same 404; an expired, accepted or revoked invitation is a 409. For a removed member the resend re-attempts the sign-in unban first; if it fails again the link is unchanged and the answer is the create door's named 502.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | — |
Responses
- 200Invitation re-sent with a fresh token and window
CpaTeamInvitationResendResponseField Type Required Description idstringrequired — statusstringrequired — expires_atstringrequired — email_sentbooleanrequired — email_failure_reasonenum: "suppressed_non_prod" | "rate_limited" | "rejected" | "unavailable" | …optional — - 400The id is not a uuid
TeamMemberError - 401No session
TeamMemberError - 403Caller is not a firm admin, or not a session
TeamMemberError - 404Invitation not found in your firm (existence-opaque)
TeamMemberError - 409The invitation can no longer be resent
TeamMemberError - 429Too many resends from this client; retry after a minute
TeamMemberError - 500Server error — sanitized (`SanitizedServerError`), or a signed-in session with no profile gets `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse) - 502invite_unban_failed — the removed member's sign-in still could not be restored; the link is unchanged and no e-mail was sent.
CpaTeamInvitationUnbanFailed
WageHour2
GET/api/wage-hourList W&H investigations
List Wage & Hour investigations (compliance category #5 — DOL). Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | optional | — |
status | query | string | optional | — |
investigation_type | query | string | optional | — |
limit | query | integer | optional | — |
offset | query | integer | optional | — |
Responses
- 200Investigations
WageHourListResponseField Type Required Description investigationsarray<WageHourInvestigation>required — totalintegerrequired — - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/wage-hourCreate W&H investigation
Create a Wage & Hour investigation record. Bearer keys gate via `notices.write`.
notices.writeRequest body WageHourCreateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
investigation_type | string | required | — |
notice_id | string · uuid | optional | — |
investigation_number | string | optional | — |
complaint_type | string | optional | — |
investigation_scope | string | optional | — |
back_wages_owed | number | optional | — |
penalty_amount | number | optional | — |
workers_affected | integer | optional | — |
classification_issue | string | optional | — |
misclassified_count | integer | optional | — |
overtime_hours_owed | number | optional | — |
overtime_rate | number | optional | — |
notice_date | string | optional | — |
response_deadline | string | optional | — |
hearing_date | string | optional | — |
investigator_name | string | optional | — |
agency | string | optional | — |
Responses
- 201Investigation created
WageHourCreateResponseField Type Required Description investigationWageHourInvestigationrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Entity not found in caller's tenant
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
WorkersComp6
GET/api/workers-compList W/C policies
List workers' comp policies for the caller's tenant (compliance category #3). Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | optional | — |
status | query | string | optional | — |
limit | query | integer | optional | — |
offset | query | integer | optional | — |
Responses
- 200Policies
WorkersCompPoliciesListResponseField Type Required Description policiesarray<WorkersCompPolicy>required — totalintegerrequired — - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/workers-compCreate W/C policy
Create a workers' comp policy. Bearer keys gate via `notices.write`.
notices.writeRequest body WorkersCompPolicyCreateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
policy_number | string | required | — |
carrier | string | required | — |
state | string | required | — |
e_mod | number | optional | — |
annual_premium | number | optional | — |
status | string | optional | — |
effective_date | string | optional | — |
expiration_date | string | optional | — |
Responses
- 201Policy created
WorkersCompPolicyCreateResponseField Type Required Description policyWorkersCompPolicyrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Entity not found in caller's tenant
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/workers-comp/auditsList W/C audits
List workers' comp audits for the caller's tenant. Each row carries an inlined dispute recommendation when both original_premium and audited_premium are present. Bearer keys gate via `notices.read`.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | query | string · uuid | optional | — |
status | query | string | optional | — |
limit | query | integer | optional | — |
offset | query | integer | optional | — |
Responses
- 200Audits
WorkersCompAuditsListResponseField Type Required Description auditsarray<WorkersCompAudit>required — totalintegerrequired — - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/workers-comp/auditsCreate W/C audit
Create a workers' comp audit record. Bearer keys gate via `notices.write`. Cookie sessions go through the Stripe subscription gate; Bearer keys bypass.
notices.writeRequest body WorkersCompAuditCreateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string · uuid | required | — |
policy_id | string · uuid | optional | — |
notice_id | string · uuid | optional | — |
audit_type | string | optional | — |
period_start | string | optional | — |
period_end | string | optional | — |
original_premium | number | optional | — |
audited_premium | number | optional | — |
deadline | string | optional | — |
Responses
- 201Audit created
WorkersCompAuditCreateResponseField Type Required Description auditWorkersCompAuditrequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 402Subscription required (cookie-session callers only)
MiscUnauthorizedResponse - 404Entity not found in caller's tenant
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/workers-comp/calculate-emodCalculate W/C e-mod
Stateless e-mod (experience modifier) calculator for workers' comp. No tenant data is read or written. Bearer keys gate via `notices.read`.
notices.readRequest body WorkersCompEmodBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
actualLosses | number | required | — |
expectedLosses | number | required | — |
credibilityWeight | number | optional | — |
ballastValue | number | optional | — |
Responses
- 200e-mod calculation, or the named refusal (`resultState: "pending_data"` + `missing_inputs`) when expected losses ≤ 0 or the credibility weight / ballast is not sent
WorkersCompEmodResponse - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error — a route-level 5xx leaves sanitized (`SanitizedServerError`: the generic message + `requestId?`), or a signed-in session with no profile gets the auth wrapper's `AccountSetupIncompleteResponse`
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Workflows6
GET/api/workflowsList workflow rules
List workflow rules for the caller's tenant.
notices.readResponses
- 200Rules
WorkflowRulesListResponseField Type Required Description rulesarray<WorkflowRule>required — - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
POST/api/workflowsCreate workflow rule
Create a workflow rule. Trigger must be one of `notice.created`, `notice.deadline_approaching`, `notice.status_changed`, or `document.uploaded`.
notices.writeRequest body WorkflowRuleCreateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
name | string | required | — |
trigger | enum: "notice.created" | "notice.deadline_approaching" | "notice.status_changed" | "document.uploaded" | required | — |
conditions | array<WorkflowCondition> | optional | — |
actions | array<WorkflowAction> | optional | — |
Responses
- 201Rule created
WorkflowRuleResponseField Type Required Description ruleWorkflowRulerequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
PATCH/api/workflows/{id}Update workflow rule
Update a workflow rule (toggle is_active, edit fields).
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Workflow rule UUID |
Request body WorkflowRuleUpdateBodyrequired
| Field | Type | Required | Description |
|---|---|---|---|
name | string | optional | — |
trigger | enum: "notice.created" | "notice.deadline_approaching" | "notice.status_changed" | "document.uploaded" | optional | — |
conditions | array<WorkflowCondition> | optional | — |
actions | array<WorkflowAction> | optional | — |
is_active | boolean | optional | — |
Responses
- 200Rule updated
WorkflowRuleResponseField Type Required Description ruleWorkflowRulerequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Rule not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
DELETE/api/workflows/{id}Delete workflow rule
Delete a workflow rule.
notices.writeParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | Workflow rule UUID |
Responses
- 200Rule deleted
objectField Type Required Description successenum: truerequired — - 400Validation error
MiscValidationErrorResponse - 401Unauthorized
MiscUnauthorizedResponse - 404Rule not found
MiscNotFoundResponse - 429Rate limit exceeded
MiscRateLimitResponse - 500Server error
one of (SanitizedServerError · AccountSetupIncompleteResponse)
GET/api/packetsList audit packs
Every response packet in the caller's tenant, newest first, each stamped with its seal state and document count. A `client_user` is narrowed to packs whose parent notice belongs to one of its entities.
notices.readResponses
- 200The tenant's audit packs.
PacketListResponseField Type Required Description okenum: truerequired — packetsarray<PacketListItem>required — - 403Authenticated but the Bearer key does not carry the declared scope.
- 500The read failed, or a post-fetch tenant-scope leak was detected (defense-in-depth third wall). The failure body repeats `packets: []` so a client cannot mistake it for an empty tenant without reading `error`.
one of (PacketsListServerError · AccountSetupIncompleteResponse)
GET/api/packets/{id}/verifyVerify an audit pack's seal
Replay the pack's manifest and report the seal state. Structural by default; `?deep=1` additionally re-downloads every document and re-hashes its bytes. Every verification is recorded as one `packet_verification_run` audit event. An unknown pack, a pack in another tenant and a pack outside a `client_user`'s entities all answer the SAME 404 — the refusal is not an existence oracle.
notices.readParameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string · uuid | required | — |
deep | query | string | optional | `1` runs the byte-level re-hash pass. Any other value (or absent) is structural-only, and `content_ok` comes back null. |
Responses
- 200Seal verification result.
PacketVerificationResponseField Type Required Description okenum: truerequired — sealedbooleanrequired True once the pack has a sealed root (`sealed_at` AND `content_hash` set). hash_versionintegerrequired — content_hashstringrequired — structural_okbooleanrequired True when the replayed manifest root matches the sealed `content_hash`. FALSE also when the pack is unsealed — an unsealed pack has nothing to attest. content_okbooleanrequired Deep byte pass: NULL when `?deep=1` was not requested, true when every non-placeholder document re-hashes to its leaf, false when at least one drifted. first_bad_docstringrequired Id of the FIRST `packet_documents` row that failed the deep byte check, or null when none failed / deep was not run. verdictenum: "verified" | "tampered" | "unsealed"required The verdict in one word, derived from the fields above by the same function the product panel renders. verified_atstringrequired ISO-8601 instant the verdict was reached. buildstringrequired The commit the server was running when it reached the verdict — the value `GET /api/health` reports as `commit` (`unknown` when the server has no build sha). verdict_event_idstringrequired Id of the `audit_events` row (`packet_verification_run`) this verification was recorded as, or null when that write failed. - 403Authenticated but the Bearer key does not carry the declared scope.
- 404Pack not found, in another tenant, or out of a `client_user`'s scope. One body for all three inputs.
- 500Verification raised, or a post-fetch tenant-scope leak was detected on the parent-notice read.
one of (SanitizedServerError · AccountSetupIncompleteResponse)
Build on the same spec.
Create a key scoped to your firm, or talk through an integration with us.