@1claw/openapi-spec 0.59.10 → 0.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +21 -1
  2. package/openapi.json +3306 -349
  3. package/openapi.yaml +2013 -55
  4. package/package.json +1 -1
package/openapi.yaml CHANGED
@@ -2,7 +2,7 @@ openapi: 3.1.0
2
2
 
3
3
  info:
4
4
  title: 1Claw API
5
- version: "0.59.10"
5
+ version: "0.60.0"
6
6
  description: |
7
7
  Secure secret management for AI agents. Provides vaults, secrets,
8
8
  policy-based access control, agent identity, Intents API,
@@ -836,7 +836,21 @@ paths:
836
836
  post:
837
837
  tags: [Approvals]
838
838
  summary: Request human approval (agent-only)
839
- description: Agents can request policy changes or other sensitive actions that require human approval.
839
+ description: |
840
+ Ask a human to approve an action.
841
+
842
+ Two families of action are accepted:
843
+
844
+ * the control-plane requests `access_request`, `policy_request`
845
+ and `binding_request`, which widen the agent's own authority; and
846
+ * business actions named `namespace.verb` (`refund.create`,
847
+ `social.post`), whose meaning is carried by `summary` and `payload`.
848
+
849
+ Actions that 1Claw itself executes on approval — `policy_change`,
850
+ `card_order`, `agent_transaction`, `agent_execution`,
851
+ `agent_sign_intent` — are created by the platform and rejected here,
852
+ because the summary a human reads would be agent-supplied while the
853
+ side effect would not be.
840
854
  operationId: requestApproval
841
855
  requestBody:
842
856
  required: true
@@ -848,20 +862,42 @@ paths:
848
862
  properties:
849
863
  action:
850
864
  type: string
851
- description: "Type of action (e.g. policy_change)"
865
+ maxLength: 64
866
+ description: "Control-plane action, or a business action named `namespace.verb`."
867
+ example: refund.create
852
868
  target_type:
853
869
  type: string
854
870
  target_id:
855
871
  type: string
856
872
  summary:
857
873
  type: object
858
- description: "JSON payload describing the request"
874
+ description: "What the human is shown: title, body, and key-value fields."
875
+ example: {title: "Refund $49.99", body: "Order #1234 arrived damaged."}
876
+ payload:
877
+ type: object
878
+ description: |
879
+ What the action will actually do. The enforced risk tier and
880
+ the human-readable line are derived from this, not from
881
+ `summary` — the two can disagree, and only this one describes
882
+ what happens if the human approves.
883
+ example: {amount_usd: "49.99", customer_email: "a.user@example.com"}
859
884
  reason:
860
885
  type: string
886
+ declared_risk_tier:
887
+ type: integer
888
+ minimum: 1
889
+ maximum: 3
890
+ description: |
891
+ Advisory. The server derives the enforced tier from the agent's
892
+ `action_approval_policy` and the payload, then takes the higher
893
+ of the two: a caller may raise its own bar, never lower it.
894
+ The response returns both.
861
895
  risk_tier:
862
896
  type: integer
863
897
  minimum: 1
864
- maximum: 5
898
+ maximum: 3
899
+ deprecated: true
900
+ description: "Former name for `declared_risk_tier`. Still accepted."
865
901
  responses:
866
902
  "202":
867
903
  description: Approval request created
@@ -6662,6 +6698,224 @@ paths:
6662
6698
  "404":
6663
6699
  $ref: "#/components/responses/NotFound"
6664
6700
 
6701
+ /v1/platform/apps/{appId}/fleets/{template_id}:
6702
+ get:
6703
+ tags: [Platform]
6704
+ summary: Fleet summary for a template
6705
+ description: >
6706
+ Every agent this template provisioned, as one cohort: how many there are, how
6707
+ they split across the template versions they were built from, and how many a
6708
+ previous rollout declined to touch. plt_ or user JWT.
6709
+ operationId: getFleet
6710
+ security:
6711
+ - BearerAuth: []
6712
+ parameters:
6713
+ - in: path
6714
+ name: appId
6715
+ required: true
6716
+ schema:
6717
+ type: string
6718
+ format: uuid
6719
+ - in: path
6720
+ name: template_id
6721
+ required: true
6722
+ schema:
6723
+ type: string
6724
+ format: uuid
6725
+ responses:
6726
+ "200":
6727
+ description: Fleet summary
6728
+ content:
6729
+ application/json:
6730
+ schema:
6731
+ $ref: "#/components/schemas/FleetSummaryResponse"
6732
+ "404":
6733
+ $ref: "#/components/responses/NotFound"
6734
+ /v1/platform/apps/{appId}/fleets/{template_id}/agents:
6735
+ get:
6736
+ tags: [Platform]
6737
+ summary: List the agents in a fleet
6738
+ operationId: listFleetAgents
6739
+ security:
6740
+ - BearerAuth: []
6741
+ parameters:
6742
+ - in: path
6743
+ name: appId
6744
+ required: true
6745
+ schema:
6746
+ type: string
6747
+ format: uuid
6748
+ - in: path
6749
+ name: template_id
6750
+ required: true
6751
+ schema:
6752
+ type: string
6753
+ format: uuid
6754
+ - in: query
6755
+ name: limit
6756
+ schema:
6757
+ type: integer
6758
+ default: 50
6759
+ minimum: 1
6760
+ maximum: 500
6761
+ - in: query
6762
+ name: offset
6763
+ schema:
6764
+ type: integer
6765
+ default: 0
6766
+ minimum: 0
6767
+ responses:
6768
+ "200":
6769
+ description: Agents in the fleet
6770
+ content:
6771
+ application/json:
6772
+ schema:
6773
+ $ref: "#/components/schemas/ListFleetAgentsResponse"
6774
+ "404":
6775
+ $ref: "#/components/responses/NotFound"
6776
+ /v1/platform/apps/{appId}/fleets/{template_id}/bulk-patch:
6777
+ post:
6778
+ tags: [Platform]
6779
+ summary: Patch every agent in a fleet
6780
+ description: >
6781
+ Applies one patch to every agent in the cohort. The field allowlist is
6782
+ deliberately narrower than a single-agent PATCH: guardrails and capability
6783
+ flags (intents_api_enabled, execution_intents_enabled) cannot be changed here,
6784
+ because at cohort scale nobody reviews the change per agent. Read the
6785
+ allowlist from bulk_patchable_fields on the fleet summary rather than hard-coding
6786
+ it. A field outside it returns 400 naming the field.
6787
+ operationId: bulkPatchFleet
6788
+ security:
6789
+ - BearerAuth: []
6790
+ parameters:
6791
+ - in: path
6792
+ name: appId
6793
+ required: true
6794
+ schema:
6795
+ type: string
6796
+ format: uuid
6797
+ - in: path
6798
+ name: template_id
6799
+ required: true
6800
+ schema:
6801
+ type: string
6802
+ format: uuid
6803
+ requestBody:
6804
+ required: true
6805
+ content:
6806
+ application/json:
6807
+ schema:
6808
+ type: object
6809
+ required: [patch]
6810
+ properties:
6811
+ patch:
6812
+ type: object
6813
+ additionalProperties: true
6814
+ description: Fields to set on every agent in the cohort.
6815
+ responses:
6816
+ "200":
6817
+ description: Patch applied
6818
+ content:
6819
+ application/json:
6820
+ schema:
6821
+ type: object
6822
+ properties:
6823
+ fields_applied:
6824
+ type: array
6825
+ items: { type: string }
6826
+ agents_matched: { type: integer, format: int64 }
6827
+ agents_updated: { type: integer, format: int64 }
6828
+ "400":
6829
+ description: A field outside the bulk-patch allowlist, or an empty patch
6830
+ "404":
6831
+ $ref: "#/components/responses/NotFound"
6832
+ /v1/platform/apps/{appId}/fleets/{template_id}/rollout:
6833
+ post:
6834
+ tags: [Platform]
6835
+ summary: Roll the current template version out to its fleet
6836
+ description: >
6837
+ Brings every agent in the cohort up to the template's current version. An agent
6838
+ changed outside fleet control is skipped rather than corrected, and the fields
6839
+ that caused the skip are recorded on it. force=true overrides that skip but
6840
+ still cannot carry a guardrail or a capability flag. dry_run=true reports what
6841
+ would happen and claims nothing, so it never blocks a real rollout. Only one
6842
+ rollout may run per template at a time; a second returns 409.
6843
+ operationId: rolloutFleet
6844
+ security:
6845
+ - BearerAuth: []
6846
+ parameters:
6847
+ - in: path
6848
+ name: appId
6849
+ required: true
6850
+ schema:
6851
+ type: string
6852
+ format: uuid
6853
+ - in: path
6854
+ name: template_id
6855
+ required: true
6856
+ schema:
6857
+ type: string
6858
+ format: uuid
6859
+ requestBody:
6860
+ required: false
6861
+ content:
6862
+ application/json:
6863
+ schema:
6864
+ type: object
6865
+ properties:
6866
+ force:
6867
+ type: boolean
6868
+ default: false
6869
+ description: Overwrite hand edits. Cannot carry guardrails.
6870
+ dry_run:
6871
+ type: boolean
6872
+ default: false
6873
+ description: Report the plan without applying it.
6874
+ responses:
6875
+ "200":
6876
+ description: Rollout result
6877
+ content:
6878
+ application/json:
6879
+ schema:
6880
+ $ref: "#/components/schemas/FleetRolloutResponse"
6881
+ "409":
6882
+ description: A rollout is already running for this template
6883
+ "404":
6884
+ $ref: "#/components/responses/NotFound"
6885
+ /v1/platform/apps/{appId}/fleets/{template_id}/pause:
6886
+ post:
6887
+ tags: [Platform]
6888
+ summary: Deactivate every agent in a fleet
6889
+ description: >
6890
+ Sets is_active=false on the whole cohort. The blast radius is the point: this
6891
+ exists for the moment an operator needs a thousand agents to stop at once.
6892
+ operationId: pauseFleet
6893
+ security:
6894
+ - BearerAuth: []
6895
+ parameters:
6896
+ - in: path
6897
+ name: appId
6898
+ required: true
6899
+ schema:
6900
+ type: string
6901
+ format: uuid
6902
+ - in: path
6903
+ name: template_id
6904
+ required: true
6905
+ schema:
6906
+ type: string
6907
+ format: uuid
6908
+ responses:
6909
+ "200":
6910
+ description: Agents paused
6911
+ content:
6912
+ application/json:
6913
+ schema:
6914
+ type: object
6915
+ properties:
6916
+ agents_paused: { type: integer, format: int64 }
6917
+ "404":
6918
+ $ref: "#/components/responses/NotFound"
6665
6919
  /v1/platform/apps/{appId}/templates/{template_id}/preview:
6666
6920
  post:
6667
6921
  tags: [Platform]
@@ -11345,85 +11599,1409 @@ paths:
11345
11599
  "200":
11346
11600
  description: Webhook processed
11347
11601
 
11348
- /v1/webhooks/discord/{webhook_path}:
11349
- post:
11350
- tags: [Agent Channels]
11351
- summary: Discord webhook
11352
- description: Public webhook endpoint for receiving Discord bot interactions.
11353
- operationId: discordWebhook
11354
- security: []
11602
+ /v1/platform/apps/{app_id}/usage:
11603
+ get:
11604
+ tags: [Platform API]
11605
+ summary: Usage for every connection on an app
11606
+ description: |
11607
+ Billable activity for the current month, grouped by end-user connection,
11608
+ **plus what could not be charged to one**.
11609
+
11610
+ The `unattributed` block is not an implementation detail. Summing only the
11611
+ per-connection numbers gives a figure that will not match the invoice you
11612
+ are reconciling against, and the gap is usage that belongs to a real
11613
+ end-user we cannot name.
11614
+
11615
+ Two kinds, deliberately kept apart:
11616
+
11617
+ * `none` — no platform linkage at all. Normal for most traffic, not a problem.
11618
+ * `ambiguous` — the agent belongs to several connections and no
11619
+ `X-Platform-Connection` header said which. This usage belongs to
11620
+ *someone*. `has_ambiguous_usage` flags it so you do not have to notice
11621
+ a non-zero nested number.
11622
+
11623
+ `totals` is derived from the parts, never queried separately, so it cannot
11624
+ disagree with its own breakdown.
11625
+ operationId: getAppUsage
11355
11626
  parameters:
11356
- - name: webhook_path
11627
+ - name: app_id
11357
11628
  in: path
11358
11629
  required: true
11359
- schema:
11360
- type: string
11361
- requestBody:
11362
- required: true
11363
- content:
11364
- application/json:
11365
- schema:
11366
- type: object
11630
+ schema: { type: string, format: uuid }
11367
11631
  responses:
11368
11632
  "200":
11369
- description: Webhook processed
11370
-
11371
- # ---------------------------------------------------------------------------
11372
- # OAuth Connect
11373
- # ---------------------------------------------------------------------------
11633
+ description: Usage report
11634
+ content:
11635
+ application/json:
11636
+ schema:
11637
+ $ref: "#/components/schemas/AppUsageReport"
11638
+ "404":
11639
+ $ref: "#/components/responses/NotFound"
11374
11640
 
11375
- /v1/oauth/providers:
11641
+ /v1/platform/apps/{app_id}/usage/export:
11376
11642
  get:
11377
- tags: [OAuth Connect]
11378
- summary: List OAuth providers
11643
+ tags: [Platform API]
11644
+ summary: Usage as CSV for billing reconciliation
11379
11645
  description: |
11380
- Returns the list of supported OAuth providers with their metadata,
11381
- available scopes, and authorization URLs. No authentication required.
11382
- operationId: listOAuthProviders
11383
- security: []
11646
+ The same report as `GET /usage`, as CSV. Includes the `ambiguous`, `none`
11647
+ and `total` rows a CSV listing only connections looks complete and is
11648
+ not, and whoever imports it has no way to tell.
11649
+ operationId: exportAppUsage
11650
+ parameters:
11651
+ - name: app_id
11652
+ in: path
11653
+ required: true
11654
+ schema: { type: string, format: uuid }
11384
11655
  responses:
11385
11656
  "200":
11386
- description: Provider list
11657
+ description: CSV
11387
11658
  content:
11388
- application/json:
11389
- schema:
11390
- $ref: "#/components/schemas/OAuthProviderListResponse"
11659
+ text/csv:
11660
+ schema: { type: string }
11661
+ "404":
11662
+ $ref: "#/components/responses/NotFound"
11391
11663
 
11392
- /v1/agents/{agent_id}/oauth/connect:
11664
+ /v1/org/apply:
11393
11665
  post:
11394
- tags: [OAuth Connect]
11395
- summary: Initiate OAuth connection
11666
+ tags: [Organization]
11667
+ summary: Apply a chart
11396
11668
  description: |
11397
- Start an OAuth authorization flow for the specified agent and provider.
11398
- Returns the authorization URL to redirect the user to. Human-only.
11399
- operationId: connectOAuth
11400
- parameters:
11401
- - $ref: "#/components/parameters/AgentId"
11669
+ Create what the chart describes. Human users only.
11670
+
11671
+ **Apply calls the same handlers the HTTP routes call.** Creating a vault
11672
+ runs five gates before anything is written — the delegation scope, a name
11673
+ check, control-plane consensus, a creation rate limit and the tier quota
11674
+ — and creating an agent runs its own. A reconciler that wrote through the
11675
+ repositories would skip all of them and would look, in review, exactly
11676
+ like one that did not.
11677
+
11678
+ So if your org has consensus configured on `vault.create`, applying a
11679
+ chart queues an approval exactly as a dashboard click would. That resource
11680
+ comes back as `awaiting_approval` rather than failing the whole chart.
11681
+
11682
+ Per-resource results: `created`, `unchanged`, `skipped`, `refused`,
11683
+ `awaiting_approval`, `failed`. `needs_attention` is true when the chart is
11684
+ not fully applied — something is waiting on a person, whether an approval,
11685
+ an OAuth sign-in, or a resource that drifted and was left alone.
11686
+
11687
+ Save `applied_state` to `.1claw/apply-state.json`. It records what apply
11688
+ set, which is what lets the next run tell drift from a first apply.
11689
+
11690
+ v1 creates and reports; it does not delete, prune, or patch in place. An
11691
+ apply that silently deletes is an apply nobody runs twice.
11692
+ operationId: applyChart
11402
11693
  requestBody:
11403
11694
  required: true
11404
11695
  content:
11405
11696
  application/json:
11406
11697
  schema:
11407
- $ref: "#/components/schemas/ConnectOAuthRequest"
11698
+ type: object
11699
+ required: [chart]
11700
+ properties:
11701
+ chart:
11702
+ type: object
11703
+ description: A chart document — `apiVersion`, `kind`, `metadata`, `spec`.
11704
+ applied_state:
11705
+ type: object
11706
+ additionalProperties: true
11408
11707
  responses:
11409
11708
  "200":
11410
- description: Authorization URL generated
11709
+ description: What happened to each resource
11411
11710
  content:
11412
11711
  application/json:
11413
11712
  schema:
11414
- $ref: "#/components/schemas/ConnectOAuthResponse"
11713
+ type: object
11714
+ properties:
11715
+ chart_name: { type: string }
11716
+ resources:
11717
+ type: array
11718
+ items:
11719
+ type: object
11720
+ properties:
11721
+ kind: { type: string }
11722
+ name: { type: string }
11723
+ result:
11724
+ type: string
11725
+ enum: [created, unchanged, skipped, refused, awaiting_approval, failed]
11726
+ id: { type: string, format: uuid }
11727
+ detail: { type: string }
11728
+ warnings:
11729
+ type: array
11730
+ items: { type: string }
11731
+ applied_state:
11732
+ type: object
11733
+ additionalProperties: true
11734
+ description: Save to `.1claw/apply-state.json`.
11735
+ needs_attention:
11736
+ type: boolean
11737
+ description: The chart is not fully applied — something is waiting on a person.
11415
11738
  "400":
11416
11739
  $ref: "#/components/responses/BadRequest"
11417
11740
  "403":
11418
11741
  $ref: "#/components/responses/Forbidden"
11419
- "404":
11420
- $ref: "#/components/responses/NotFound"
11421
11742
 
11422
- /v1/agents/{agent_id}/oauth/connections:
11743
+ /v1/org/approval-learning/shadow-report:
11423
11744
  get:
11424
- tags: [OAuth Connect]
11425
- summary: List OAuth connections
11426
- description: List all active OAuth connections for the specified agent.
11745
+ tags: [Organization]
11746
+ summary: What would have been approved automatically
11747
+ description: |
11748
+ Every approval decision is observed, per **fingerprint bucket** — the
11749
+ action, the amount band, whether the recipient was new, and who they
11750
+ were. This reports the buckets a person has approved without exception,
11751
+ and what promoting one would write into an agent's policy.
11752
+
11753
+ Observing is not acting. In the default `shadow` mode nothing changes who
11754
+ gets asked; `can_promote` is false and the promote endpoint refuses.
11755
+
11756
+ Each suggestion carries `would_write_rule` — the actual rule, built by the
11757
+ same function promotion uses, so the report cannot promise something
11758
+ promotion would refuse.
11759
+ operationId: getApprovalLearningShadowReport
11760
+ responses:
11761
+ "200":
11762
+ description: Report
11763
+ content:
11764
+ application/json:
11765
+ schema:
11766
+ type: object
11767
+ properties:
11768
+ mode: { type: string, enum: [shadow, enforce] }
11769
+ threshold:
11770
+ type: integer
11771
+ description: Consecutive approvals in one bucket before it appears here.
11772
+ can_promote: { type: boolean }
11773
+ observed_buckets: { type: integer }
11774
+ total_decisions: { type: integer }
11775
+ suggestions:
11776
+ type: array
11777
+ items:
11778
+ type: object
11779
+ properties:
11780
+ profile_id: { type: string, format: uuid }
11781
+ action_type: { type: string }
11782
+ fingerprint_bucket: { type: string, example: "refund.create|0-10|known|a@b.co" }
11783
+ consecutive_approvals: { type: integer }
11784
+ total_requests: { type: integer }
11785
+ would_write_rule: { type: object, nullable: true }
11786
+ blocked:
11787
+ type: string
11788
+ description: Present when a bound prevents promotion, saying which.
11789
+ last_decision_at: { type: string, format: date-time }
11790
+ "403":
11791
+ $ref: "#/components/responses/Forbidden"
11792
+
11793
+ /v1/org/approval-learning/{profile_id}/promote:
11794
+ post:
11795
+ tags: [Organization]
11796
+ summary: Turn an observed pattern into a policy rule
11797
+ description: |
11798
+ Writes a rule into the named agent's `action_approval_policy`. Human users
11799
+ only, and only when the organisation is in `enforce` mode.
11800
+
11801
+ **The rule covers only what was actually approved.** Five approved $5
11802
+ refunds to one customer produce `{ refund.create, asks above $10, that
11803
+ recipient }` — a $49 request does not match it and still reaches a human.
11804
+
11805
+ Refused when: fewer than five consecutive approvals; *any* past rejection
11806
+ on the bucket (a long recent run must not hide a history of refusals); the
11807
+ bucket is for a recipient never paid before; the amount band has no upper
11808
+ edge; or the action grants or destroys authority.
11809
+
11810
+ `widen_to_action_type` drops the amount and recipient constraints. It is
11811
+ never the default and should be an explicit choice in your UI, not a
11812
+ checkbox someone skims past.
11813
+
11814
+ The written rule is marked `promoted_from_learning` so an operator can
11815
+ tell it apart from one they wrote, and it replaces any existing rule for
11816
+ the same action type — appending would leave two rules where only the
11817
+ first ever applies.
11818
+ operationId: promoteApprovalLearningProfile
11819
+ parameters:
11820
+ - name: profile_id
11821
+ in: path
11822
+ required: true
11823
+ schema: { type: string, format: uuid }
11824
+ requestBody:
11825
+ required: true
11826
+ content:
11827
+ application/json:
11828
+ schema:
11829
+ type: object
11830
+ required: [agent_id]
11831
+ properties:
11832
+ agent_id: { type: string, format: uuid }
11833
+ widen_to_action_type:
11834
+ type: boolean
11835
+ default: false
11836
+ responses:
11837
+ "200":
11838
+ description: Promoted
11839
+ content:
11840
+ application/json:
11841
+ schema:
11842
+ type: object
11843
+ properties:
11844
+ profile_id: { type: string, format: uuid }
11845
+ agent_id: { type: string, format: uuid }
11846
+ rule: { type: object }
11847
+ "400":
11848
+ $ref: "#/components/responses/BadRequest"
11849
+ "403":
11850
+ $ref: "#/components/responses/Forbidden"
11851
+ "404":
11852
+ $ref: "#/components/responses/NotFound"
11853
+ "409":
11854
+ $ref: "#/components/responses/Conflict"
11855
+
11856
+ /v1/policy-presets:
11857
+ get:
11858
+ tags: [Policies]
11859
+ summary: Named starting points for an agent's policy
11860
+ description: |
11861
+ Four presets an operator can choose between without reading a policy
11862
+ document. Public — a description of what 1Claw offers, not tenant data.
11863
+
11864
+ Each carries a `headline`: the one-line consequence someone should read
11865
+ before choosing it, in the words they would use.
11866
+ operationId: listPolicyPresets
11867
+ security: []
11868
+ responses:
11869
+ "200":
11870
+ description: Catalogue
11871
+ content:
11872
+ application/json:
11873
+ schema:
11874
+ type: object
11875
+ properties:
11876
+ presets: { type: array, items: { type: object } }
11877
+
11878
+ /v1/agents/{agent_id}/policy-preset/preview:
11879
+ post:
11880
+ tags: [Policies]
11881
+ summary: What a preset would change
11882
+ description: |
11883
+ Compiles a preset against this agent and reports which fields it would
11884
+ **widen** — loosen relative to what the agent can already do.
11885
+
11886
+ Widening detection errs toward flagging: a false positive costs one extra
11887
+ approval, a false negative is a limit raised without review. Enabling a
11888
+ capability widens; disabling does not. Raising a cap widens; setting a
11889
+ first cap does not, because absent means unlimited.
11890
+ operationId: previewPolicyPreset
11891
+ parameters:
11892
+ - name: agent_id
11893
+ in: path
11894
+ required: true
11895
+ schema: { type: string, format: uuid }
11896
+ requestBody:
11897
+ required: true
11898
+ content:
11899
+ application/json:
11900
+ schema:
11901
+ type: object
11902
+ required: [preset]
11903
+ properties:
11904
+ preset: { type: string, example: small-business-spender }
11905
+ responses:
11906
+ "200":
11907
+ description: Proposal
11908
+ content:
11909
+ application/json:
11910
+ schema:
11911
+ type: object
11912
+ properties:
11913
+ preset_slug: { type: string }
11914
+ guardrails: { type: object }
11915
+ action_approval_policy: { type: object }
11916
+ access_policy: { type: object }
11917
+ widens:
11918
+ type: array
11919
+ items: { type: string }
11920
+ description: Fields this preset would loosen. Show these, not a generic warning.
11921
+ requires_guardrail_approval: { type: boolean }
11922
+ explanation: { type: string }
11923
+ "403":
11924
+ $ref: "#/components/responses/Forbidden"
11925
+ "404":
11926
+ $ref: "#/components/responses/NotFound"
11927
+
11928
+ /v1/agents/{agent_id}/policy-preset:
11929
+ post:
11930
+ tags: [Policies]
11931
+ summary: Apply a policy preset
11932
+ description: |
11933
+ **Applies through the agent update handler**, not by writing guardrail
11934
+ columns. A preset that wrote them directly would be a way around the
11935
+ guardrail widening approval flow wearing a friendlier interface.
11936
+
11937
+ So if the preset loosens something and your organisation gates that, you
11938
+ get the same **202 with a pending approval** you would get from editing
11939
+ the agent by hand — not a quietly applied change. Pass `approval_id`
11940
+ once that approval is granted.
11941
+ operationId: applyPolicyPreset
11942
+ parameters:
11943
+ - name: agent_id
11944
+ in: path
11945
+ required: true
11946
+ schema: { type: string, format: uuid }
11947
+ requestBody:
11948
+ required: true
11949
+ content:
11950
+ application/json:
11951
+ schema:
11952
+ type: object
11953
+ required: [preset]
11954
+ properties:
11955
+ preset: { type: string }
11956
+ approval_id: { type: string, format: uuid }
11957
+ responses:
11958
+ "200":
11959
+ description: Applied
11960
+ "202":
11961
+ description: Queued behind a guardrail approval
11962
+ "403":
11963
+ $ref: "#/components/responses/Forbidden"
11964
+ "404":
11965
+ $ref: "#/components/responses/NotFound"
11966
+
11967
+ /v1/agents/{agent_id}/trust:
11968
+ get:
11969
+ tags: [Discovery]
11970
+ summary: What a listed agent has earned
11971
+ description: |
11972
+ Everything a publisher writes — name, description, tags — is a claim.
11973
+ These are the parts they cannot write: whether a human reviewed the
11974
+ listing, how many people installed it, what they rated it.
11975
+
11976
+ Public, because its purpose is to be read by someone deciding whether to
11977
+ install a stranger's agent. Only listed agents have public trust.
11978
+
11979
+ **A listing with reports shows no badges at all.** "Platform reviewed"
11980
+ beside an active complaint tells a reader the opposite of what they need.
11981
+ An average rating appears only from three reviews — one rating is not an
11982
+ average.
11983
+ operationId: getAgentTrust
11984
+ security: []
11985
+ parameters:
11986
+ - name: agent_id
11987
+ in: path
11988
+ required: true
11989
+ schema: { type: string, format: uuid }
11990
+ responses:
11991
+ "200":
11992
+ description: Trust signals
11993
+ content:
11994
+ application/json:
11995
+ schema:
11996
+ type: object
11997
+ properties:
11998
+ trust:
11999
+ type: object
12000
+ properties:
12001
+ tier: { type: string, enum: [unverified, platform_reviewed, identity_verified, enterprise] }
12002
+ install_count: { type: integer }
12003
+ avg_rating: { type: number, nullable: true }
12004
+ review_count: { type: integer }
12005
+ badges: { type: array, items: { type: string } }
12006
+ flagged_for_review: { type: boolean }
12007
+ "404":
12008
+ $ref: "#/components/responses/NotFound"
12009
+
12010
+ /v1/agents/{agent_id}/report:
12011
+ post:
12012
+ tags: [Discovery]
12013
+ summary: Report a listed agent
12014
+ description: |
12015
+ Human users only — an agent reporting another agent is a way to bury a
12016
+ competitor's listing at machine speed.
12017
+
12018
+ The response does not include the report count. Telling a reporter how
12019
+ close a listing is to being flagged tells them how many more to file.
12020
+ operationId: reportAgent
12021
+ parameters:
12022
+ - name: agent_id
12023
+ in: path
12024
+ required: true
12025
+ schema: { type: string, format: uuid }
12026
+ requestBody:
12027
+ required: true
12028
+ content:
12029
+ application/json:
12030
+ schema:
12031
+ type: object
12032
+ properties:
12033
+ reason: { type: string }
12034
+ responses:
12035
+ "202":
12036
+ description: Received
12037
+ "403":
12038
+ $ref: "#/components/responses/Forbidden"
12039
+ "404":
12040
+ $ref: "#/components/responses/NotFound"
12041
+
12042
+ /v1/agents/{agent_id}/review:
12043
+ post:
12044
+ tags: [Discovery]
12045
+ summary: Rate an agent you have used
12046
+ description: |
12047
+ One review per person per agent; a second replaces the first. You cannot
12048
+ review an agent from your own organisation.
12049
+
12050
+ Comments are shown only once moderated. The rating counts either way — a
12051
+ number is harder to abuse than free text.
12052
+ operationId: reviewAgent
12053
+ parameters:
12054
+ - name: agent_id
12055
+ in: path
12056
+ required: true
12057
+ schema: { type: string, format: uuid }
12058
+ requestBody:
12059
+ required: true
12060
+ content:
12061
+ application/json:
12062
+ schema:
12063
+ type: object
12064
+ required: [rating]
12065
+ properties:
12066
+ rating: { type: integer, minimum: 1, maximum: 5 }
12067
+ comment: { type: string }
12068
+ responses:
12069
+ "201":
12070
+ description: Recorded
12071
+ "400":
12072
+ $ref: "#/components/responses/BadRequest"
12073
+ "403":
12074
+ $ref: "#/components/responses/Forbidden"
12075
+ "404":
12076
+ $ref: "#/components/responses/NotFound"
12077
+
12078
+ /v1/peers:
12079
+ post:
12080
+ tags: [Memory]
12081
+ summary: Create a peer and name its observers
12082
+ description: |
12083
+ Human users only. Creating a peer decides which agents may read a model
12084
+ of a person, and an agent that could do that could add itself.
12085
+
12086
+ Idempotent on `(org, peer_type, peer_ref)`. Observers are **merged**, not
12087
+ replaced — a second call adding one agent does not revoke the others
12088
+ already watching. Every named observer must be an agent in this
12089
+ organisation, so a typo or an id copied from elsewhere is an error rather
12090
+ than a silent no-op that leaves an operator believing an agent is
12091
+ watching when none is.
12092
+ operationId: createPeer
12093
+ requestBody:
12094
+ required: true
12095
+ content:
12096
+ application/json:
12097
+ schema:
12098
+ type: object
12099
+ required: [peer_type, peer_ref]
12100
+ properties:
12101
+ peer_type: { type: string, enum: [user, platform_connection, external] }
12102
+ peer_ref: { type: string }
12103
+ display_name: { type: string }
12104
+ platform_connection_id: { type: string, format: uuid }
12105
+ observer_agent_ids:
12106
+ type: array
12107
+ items: { type: string, format: uuid }
12108
+ description: Empty means nobody. A peer with no observers is readable by no agent.
12109
+ responses:
12110
+ "201":
12111
+ description: Peer created or updated
12112
+ content:
12113
+ application/json:
12114
+ schema:
12115
+ type: object
12116
+ properties:
12117
+ peer: { $ref: "#/components/schemas/Peer" }
12118
+ "400":
12119
+ $ref: "#/components/responses/BadRequest"
12120
+ "403":
12121
+ $ref: "#/components/responses/Forbidden"
12122
+
12123
+ /v1/peers/{peer_id}/export:
12124
+ get:
12125
+ tags: [Memory]
12126
+ summary: Everything held about this person
12127
+ description: |
12128
+ The whole behavioural profile plus the raw observations behind it. Human
12129
+ users only, behind strong-factor re-auth — this is exactly what a stolen
12130
+ session would want.
12131
+
12132
+ Each fact carries `why_we_believe_this`: its provenance, including
12133
+ tombstones for observations that have since expired. An export listing
12134
+ conclusions without their basis answers only the easy half of the
12135
+ question.
12136
+ operationId: exportPeerData
12137
+ parameters:
12138
+ - name: peer_id
12139
+ in: path
12140
+ required: true
12141
+ schema: { type: string, format: uuid }
12142
+ responses:
12143
+ "200":
12144
+ description: Export
12145
+ content:
12146
+ application/json:
12147
+ schema:
12148
+ type: object
12149
+ properties:
12150
+ peer: { type: object }
12151
+ facts:
12152
+ type: array
12153
+ items:
12154
+ type: object
12155
+ properties:
12156
+ fact_key: { type: string }
12157
+ fact_value: { type: object }
12158
+ confidence: { type: string, nullable: true }
12159
+ why_we_believe_this: { type: array, items: { type: object } }
12160
+ corrected_by_a_human: { type: boolean }
12161
+ raw_observations: { type: array, items: { type: object } }
12162
+ note: { type: string }
12163
+ "403":
12164
+ $ref: "#/components/responses/Forbidden"
12165
+ "404":
12166
+ $ref: "#/components/responses/NotFound"
12167
+
12168
+ /v1/peers/{peer_id}/data:
12169
+ delete:
12170
+ tags: [Memory]
12171
+ summary: Forget this person
12172
+ description: |
12173
+ Deletes the peer, its facts and its observations. Human users only,
12174
+ behind strong-factor re-auth, and irreversible.
12175
+
12176
+ Returns counts of what was removed — "deleted" with no numbers is not
12177
+ something anyone can check. The audit entry records that a deletion
12178
+ happened and deliberately omits the identifier someone asked to have
12179
+ forgotten.
12180
+ operationId: deletePeerData
12181
+ parameters:
12182
+ - name: peer_id
12183
+ in: path
12184
+ required: true
12185
+ schema: { type: string, format: uuid }
12186
+ responses:
12187
+ "200":
12188
+ description: Deleted
12189
+ content:
12190
+ application/json:
12191
+ schema:
12192
+ type: object
12193
+ properties:
12194
+ deleted: { type: boolean }
12195
+ facts_deleted: { type: integer }
12196
+ observations_deleted: { type: integer }
12197
+ "403":
12198
+ $ref: "#/components/responses/Forbidden"
12199
+ "404":
12200
+ $ref: "#/components/responses/NotFound"
12201
+
12202
+ /v1/peers/{peer_id}/facts:
12203
+ patch:
12204
+ tags: [Memory]
12205
+ summary: Correct what the system believes
12206
+ description: |
12207
+ Human users only. A correction **pins** the fact: the background
12208
+ processor will not re-derive over it, because someone correcting what a
12209
+ system believes about them outranks the inference that got it wrong.
12210
+
12211
+ The correction is appended to the fact's provenance as a `human` entry,
12212
+ so the record shows both what was inferred and that a person disagreed.
12213
+ operationId: editPeerFact
12214
+ parameters:
12215
+ - name: peer_id
12216
+ in: path
12217
+ required: true
12218
+ schema: { type: string, format: uuid }
12219
+ requestBody:
12220
+ required: true
12221
+ content:
12222
+ application/json:
12223
+ schema:
12224
+ type: object
12225
+ required: [fact_key, fact_value]
12226
+ properties:
12227
+ fact_key: { type: string }
12228
+ fact_value: { type: object }
12229
+ responses:
12230
+ "200":
12231
+ description: Corrected
12232
+ content:
12233
+ application/json:
12234
+ schema:
12235
+ type: object
12236
+ properties:
12237
+ fact_key: { type: string }
12238
+ edited_by_human: { type: boolean }
12239
+ note: { type: string }
12240
+ "403":
12241
+ $ref: "#/components/responses/Forbidden"
12242
+ "404":
12243
+ $ref: "#/components/responses/NotFound"
12244
+
12245
+ /v1/peers/{peer_id}/context:
12246
+ get:
12247
+ tags: [Memory]
12248
+ summary: A context blob for prompt injection
12249
+ description: |
12250
+ What this person's history suggests, as prose an agent can put in a
12251
+ prompt. Best-supported facts first, so a tight budget drops the
12252
+ least-supported beliefs rather than an arbitrary tail — and a fact a
12253
+ human corrected sorts ahead of everything, because a correction someone
12254
+ took the trouble to make is the last thing to cut.
12255
+
12256
+ Never truncates mid-line: half a sentence about a person is worse than
12257
+ one fewer sentence. A peer with no facts returns an empty string rather
12258
+ than a header claiming to describe someone.
12259
+
12260
+ The blob ends by saying these are observations and not instructions,
12261
+ because an agent reading it needs to know the difference.
12262
+ operationId: getPeerContext
12263
+ parameters:
12264
+ - name: peer_id
12265
+ in: path
12266
+ required: true
12267
+ schema: { type: string, format: uuid }
12268
+ - name: budget
12269
+ in: query
12270
+ description: Characters. Default 2000, capped at 8000.
12271
+ schema: { type: integer }
12272
+ responses:
12273
+ "200":
12274
+ description: Context
12275
+ content:
12276
+ application/json:
12277
+ schema:
12278
+ type: object
12279
+ properties:
12280
+ context: { type: string }
12281
+ characters: { type: integer }
12282
+ budget: { type: integer }
12283
+ facts_available: { type: integer }
12284
+ "403":
12285
+ $ref: "#/components/responses/Forbidden"
12286
+ "404":
12287
+ $ref: "#/components/responses/NotFound"
12288
+
12289
+ /v1/agents/{agent_id}/peer-context:
12290
+ get:
12291
+ tags: [Memory]
12292
+ summary: An agent's own peer context
12293
+ description: |
12294
+ Resolves the peer from the agent's platform connection, so an agent does
12295
+ not need to know a peer id.
12296
+
12297
+ An agent may only ask for its own — otherwise this route would be a way
12298
+ to read a peer through an agent that observes it, from one that does not.
12299
+ The observer check still applies: being the agent named in the path is
12300
+ not the same as observing that connection's peer.
12301
+ operationId: getAgentPeerContext
12302
+ parameters:
12303
+ - name: agent_id
12304
+ in: path
12305
+ required: true
12306
+ schema: { type: string, format: uuid }
12307
+ - name: budget
12308
+ in: query
12309
+ schema: { type: integer }
12310
+ responses:
12311
+ "200":
12312
+ description: Context
12313
+ content:
12314
+ application/json:
12315
+ schema:
12316
+ type: object
12317
+ properties:
12318
+ peer_id: { type: string, format: uuid }
12319
+ context: { type: string }
12320
+ characters: { type: integer }
12321
+ "403":
12322
+ $ref: "#/components/responses/Forbidden"
12323
+ "404":
12324
+ $ref: "#/components/responses/NotFound"
12325
+
12326
+ /v1/peers/{peer_id}:
12327
+ get:
12328
+ tags: [Memory]
12329
+ summary: A peer's profile and derived facts
12330
+ description: |
12331
+ A peer is a shared model of one human, across the agents serving them.
12332
+
12333
+ **An agent reaches a peer only by being named in its observer list.**
12334
+ Being in the same organisation, the same platform connection, or holding
12335
+ a broad scope grants nothing. A peer with no observers is readable by no
12336
+ agent at all — forgetting to set observers must not expose someone's
12337
+ behavioural profile to every agent in the org.
12338
+
12339
+ A peer in another organisation returns 404, the same as an unknown id:
12340
+ whether one exists elsewhere is not something a caller should learn.
12341
+
12342
+ Each fact carries `provenance` — why it is believed. Entries for events
12343
+ that have since expired become tombstones keeping the shape of what was
12344
+ seen without the content, so a belief about a person always has a
12345
+ recoverable basis.
12346
+ operationId: getPeer
12347
+ parameters:
12348
+ - name: peer_id
12349
+ in: path
12350
+ required: true
12351
+ schema: { type: string, format: uuid }
12352
+ responses:
12353
+ "200":
12354
+ description: Peer and facts
12355
+ content:
12356
+ application/json:
12357
+ schema:
12358
+ type: object
12359
+ properties:
12360
+ peer: { $ref: "#/components/schemas/Peer" }
12361
+ facts:
12362
+ type: array
12363
+ items: { $ref: "#/components/schemas/PeerFact" }
12364
+ "403":
12365
+ $ref: "#/components/responses/Forbidden"
12366
+ "404":
12367
+ $ref: "#/components/responses/NotFound"
12368
+
12369
+ /v1/peers/{peer_id}/events:
12370
+ post:
12371
+ tags: [Memory]
12372
+ summary: Record something observed about a person
12373
+ operationId: recordPeerEvent
12374
+ parameters:
12375
+ - name: peer_id
12376
+ in: path
12377
+ required: true
12378
+ schema: { type: string, format: uuid }
12379
+ requestBody:
12380
+ required: true
12381
+ content:
12382
+ application/json:
12383
+ schema:
12384
+ type: object
12385
+ required: [event_type, content]
12386
+ properties:
12387
+ event_type:
12388
+ type: string
12389
+ enum: [message, approval, action, observation]
12390
+ content: { type: object, additionalProperties: true }
12391
+ responses:
12392
+ "201":
12393
+ description: Recorded
12394
+ content:
12395
+ application/json:
12396
+ schema:
12397
+ type: object
12398
+ properties:
12399
+ event_id: { type: string, format: uuid }
12400
+ "400":
12401
+ $ref: "#/components/responses/BadRequest"
12402
+ "403":
12403
+ $ref: "#/components/responses/Forbidden"
12404
+ "404":
12405
+ $ref: "#/components/responses/NotFound"
12406
+
12407
+ /v1/peers/{peer_id}/predict-approval:
12408
+ post:
12409
+ tags: [Memory]
12410
+ summary: How has this person decided this before?
12411
+ description: |
12412
+ Answers two different questions, and keeps them apart on purpose.
12413
+
12414
+ `likelihood` is an **observation about a person** — how they have decided
12415
+ comparable requests before. Comparable means the same fingerprint bucket,
12416
+ not the same action type: three approvals of $5 say nothing about $500.
12417
+
12418
+ `suggest_auto` is a **statement about a policy**. It is true only where a
12419
+ rule the operator already wrote would auto-approve this exact case. It is
12420
+ never derived from `likelihood`, and a confident model never becomes new
12421
+ authority. It is false, with `blocked_reason`, when:
12422
+
12423
+ * no rule covers the action (`no_matching_rule`);
12424
+ * a rule says a human decides (`rule_requires_approval`);
12425
+ * the amount is above the rule's own threshold (`above_configured_threshold`);
12426
+ * the derived risk tier is above the lowest (`risk_tier_requires_step_up`);
12427
+ * the action grants or destroys authority (`action_is_sensitive`).
12428
+
12429
+ The policy consulted is the calling agent's own, so a prediction cannot
12430
+ inherit authority from another agent that happens to observe the same
12431
+ person.
12432
+ operationId: predictApproval
12433
+ parameters:
12434
+ - name: peer_id
12435
+ in: path
12436
+ required: true
12437
+ schema: { type: string, format: uuid }
12438
+ requestBody:
12439
+ required: true
12440
+ content:
12441
+ application/json:
12442
+ schema:
12443
+ type: object
12444
+ required: [action_type, effective_risk_tier]
12445
+ properties:
12446
+ action_type: { type: string, example: refund.create }
12447
+ payload: { type: object, additionalProperties: true }
12448
+ effective_risk_tier:
12449
+ type: integer
12450
+ minimum: 1
12451
+ maximum: 3
12452
+ responses:
12453
+ "200":
12454
+ description: Prediction
12455
+ content:
12456
+ application/json:
12457
+ schema:
12458
+ type: object
12459
+ required: [reasoning, suggest_auto]
12460
+ properties:
12461
+ likelihood:
12462
+ type: number
12463
+ nullable: true
12464
+ description: Absent when there is no comparable history.
12465
+ reasoning: { type: string }
12466
+ suggest_auto:
12467
+ type: boolean
12468
+ description: Whether the operator's own policy already permits this.
12469
+ blocked_reason:
12470
+ type: string
12471
+ enum: [no_matching_rule, rule_requires_approval, above_configured_threshold, risk_tier_requires_step_up, action_is_sensitive]
12472
+ "403":
12473
+ $ref: "#/components/responses/Forbidden"
12474
+ "404":
12475
+ $ref: "#/components/responses/NotFound"
12476
+
12477
+ /v1/peers/by-connection/{connection_id}:
12478
+ get:
12479
+ tags: [Memory]
12480
+ summary: Resolve the peer for a platform connection
12481
+ operationId: getPeerByConnection
12482
+ parameters:
12483
+ - name: connection_id
12484
+ in: path
12485
+ required: true
12486
+ schema: { type: string, format: uuid }
12487
+ responses:
12488
+ "200":
12489
+ description: Peer
12490
+ content:
12491
+ application/json:
12492
+ schema:
12493
+ type: object
12494
+ properties:
12495
+ peer: { $ref: "#/components/schemas/Peer" }
12496
+ "403":
12497
+ $ref: "#/components/responses/Forbidden"
12498
+ "404":
12499
+ $ref: "#/components/responses/NotFound"
12500
+
12501
+ /v1/org/apply/diff:
12502
+ post:
12503
+ tags: [Organization]
12504
+ summary: What would this chart change?
12505
+ description: |
12506
+ Reconcile a chart against the org without changing anything. Human users
12507
+ only — a chart provisions agents, vaults and access policies, so an agent
12508
+ that could apply one could grant itself access to a vault it cannot
12509
+ currently read.
12510
+
12511
+ A POST rather than a GET because the chart is the request body, and a GET
12512
+ carrying a body is one many proxies and clients drop or mangle. Read-only
12513
+ either way.
12514
+
12515
+ The plan reports five outcomes per resource:
12516
+
12517
+ * `create` — not present, would be created.
12518
+ * `patch` — present, differs only in fields safe to change in place.
12519
+ * `unchanged` — present and matching.
12520
+ * `skipped_drifted` — **changed outside this chart**, so left alone.
12521
+ Someone edited it by hand for a reason; overwriting that because a file
12522
+ says otherwise is how a deployment tool destroys an incident fix.
12523
+ * `refused` — the chart asks for a change apply will not make. Guardrail
12524
+ fields are never patched here: editing them routes through the guardrail
12525
+ approval flow, and a reconciler writing them directly would be a way
12526
+ around it.
12527
+
12528
+ Pass `applied_state` from `.1claw/apply-state.json` so drift can be told
12529
+ from a first run — a pre-existing resource is not drifted, it was simply
12530
+ not created by this chart.
12531
+ operationId: diffChart
12532
+ requestBody:
12533
+ required: true
12534
+ content:
12535
+ application/json:
12536
+ schema:
12537
+ type: object
12538
+ required: [chart]
12539
+ properties:
12540
+ chart:
12541
+ type: object
12542
+ description: A chart document — `apiVersion`, `kind`, `metadata`, `spec`.
12543
+ applied_state:
12544
+ type: object
12545
+ additionalProperties: true
12546
+ description: What a previous apply recorded, keyed `kind/name`.
12547
+ responses:
12548
+ "200":
12549
+ description: The plan
12550
+ content:
12551
+ application/json:
12552
+ schema:
12553
+ type: object
12554
+ properties:
12555
+ chart_name: { type: string }
12556
+ actions:
12557
+ type: array
12558
+ items: { type: object }
12559
+ warnings:
12560
+ type: array
12561
+ items: { type: string }
12562
+ summary:
12563
+ type: object
12564
+ properties:
12565
+ create: { type: integer }
12566
+ patch: { type: integer }
12567
+ skipped_drifted: { type: integer }
12568
+ unchanged: { type: integer }
12569
+ refused: { type: integer }
12570
+ no_changes: { type: boolean }
12571
+ "400":
12572
+ $ref: "#/components/responses/BadRequest"
12573
+ "403":
12574
+ $ref: "#/components/responses/Forbidden"
12575
+
12576
+ /v1/notification-targets:
12577
+ get:
12578
+ tags: [Notifications]
12579
+ summary: List your notification targets
12580
+ operationId: listNotificationTargets
12581
+ responses:
12582
+ "200":
12583
+ description: Your targets
12584
+ content:
12585
+ application/json:
12586
+ schema:
12587
+ type: object
12588
+ properties:
12589
+ targets:
12590
+ type: array
12591
+ items:
12592
+ $ref: "#/components/schemas/NotificationTarget"
12593
+ post:
12594
+ tags: [Notifications]
12595
+ summary: Add a notification target
12596
+ description: |
12597
+ Where approvals and automation output reach a human: a phone number, an
12598
+ https webhook, an email address, or a push token.
12599
+
12600
+ An SMS target is created **unverified** and stays that way until someone
12601
+ proves they hold the number. Adding a number must not itself be an
12602
+ authorisation — otherwise a session borrowed for five minutes leaves
12603
+ behind a number that can approve things long after it is gone. An
12604
+ unverified target still receives notifications; it just cannot reply to
12605
+ decide one.
12606
+ operationId: createNotificationTarget
12607
+ requestBody:
12608
+ required: true
12609
+ content:
12610
+ application/json:
12611
+ schema:
12612
+ type: object
12613
+ required: [target_type, config]
12614
+ properties:
12615
+ target_type:
12616
+ type: string
12617
+ enum: [sms, webhook, expo, email]
12618
+ config:
12619
+ type: object
12620
+ description: |
12621
+ `{"phone_number": "+14155550123"}` for sms (E.164 only),
12622
+ `{"url": "https://…"}` for webhook (https only),
12623
+ `{"email": "…"}`, or `{"push_token": "…"}`.
12624
+ events:
12625
+ type: array
12626
+ items: { type: string }
12627
+ description: Empty means every event.
12628
+ agent_id:
12629
+ type: string
12630
+ format: uuid
12631
+ description: The agent whose SMS channel sends to this target.
12632
+ user_id:
12633
+ type: string
12634
+ format: uuid
12635
+ description: Defaults to the caller.
12636
+ responses:
12637
+ "201":
12638
+ description: Target created, unverified
12639
+ content:
12640
+ application/json:
12641
+ schema:
12642
+ $ref: "#/components/schemas/NotificationTarget"
12643
+ "400":
12644
+ $ref: "#/components/responses/BadRequest"
12645
+ "403":
12646
+ $ref: "#/components/responses/Forbidden"
12647
+ "409":
12648
+ $ref: "#/components/responses/Conflict"
12649
+
12650
+ /v1/notification-targets/{id}:
12651
+ delete:
12652
+ tags: [Notifications]
12653
+ summary: Remove a notification target
12654
+ operationId: deleteNotificationTarget
12655
+ parameters:
12656
+ - name: id
12657
+ in: path
12658
+ required: true
12659
+ schema: { type: string, format: uuid }
12660
+ responses:
12661
+ "204":
12662
+ description: Removed
12663
+ "403":
12664
+ $ref: "#/components/responses/Forbidden"
12665
+ "404":
12666
+ $ref: "#/components/responses/NotFound"
12667
+
12668
+ /v1/notification-targets/{id}/verify/start:
12669
+ post:
12670
+ tags: [Notifications]
12671
+ summary: Text a verification code to an SMS target
12672
+ description: |
12673
+ Sends a six-digit code from the same channel the target's notifications
12674
+ will come from — a code arriving from a different number than the one
12675
+ the recipient will later see is a code they are right to distrust.
12676
+
12677
+ Expires in 10 minutes. Five wrong answers void it.
12678
+ operationId: startNotificationTargetVerification
12679
+ parameters:
12680
+ - name: id
12681
+ in: path
12682
+ required: true
12683
+ schema: { type: string, format: uuid }
12684
+ responses:
12685
+ "200":
12686
+ description: Code sent
12687
+ content:
12688
+ application/json:
12689
+ schema:
12690
+ type: object
12691
+ properties:
12692
+ message: { type: string }
12693
+ expires_in_seconds: { type: integer }
12694
+ "400":
12695
+ $ref: "#/components/responses/BadRequest"
12696
+ "403":
12697
+ $ref: "#/components/responses/Forbidden"
12698
+ "404":
12699
+ $ref: "#/components/responses/NotFound"
12700
+
12701
+ /v1/notification-targets/{id}/verify:
12702
+ post:
12703
+ tags: [Notifications]
12704
+ summary: Submit the verification code
12705
+ operationId: completeNotificationTargetVerification
12706
+ parameters:
12707
+ - name: id
12708
+ in: path
12709
+ required: true
12710
+ schema: { type: string, format: uuid }
12711
+ requestBody:
12712
+ required: true
12713
+ content:
12714
+ application/json:
12715
+ schema:
12716
+ type: object
12717
+ required: [code]
12718
+ properties:
12719
+ code: { type: string, example: "042913" }
12720
+ responses:
12721
+ "200":
12722
+ description: Verified
12723
+ content:
12724
+ application/json:
12725
+ schema:
12726
+ type: object
12727
+ properties:
12728
+ verified: { type: boolean }
12729
+ "400":
12730
+ $ref: "#/components/responses/BadRequest"
12731
+ "403":
12732
+ $ref: "#/components/responses/Forbidden"
12733
+ "404":
12734
+ $ref: "#/components/responses/NotFound"
12735
+
12736
+ /v1/webhooks/sms/{webhook_path}:
12737
+ post:
12738
+ tags: [Channels]
12739
+ summary: Inbound SMS webhook (Twilio)
12740
+ description: |
12741
+ Called by Twilio when someone texts the channel's number. Public, because
12742
+ the provider calls it — so the `X-Twilio-Signature` header is the only
12743
+ thing establishing that a message is genuine, and it is verified over the
12744
+ exact public URL and every POST parameter.
12745
+
12746
+ A verified signature proves the message came from Twilio, **not** that it
12747
+ came from the right person: anyone who knows the number can text it and
12748
+ their message arrives correctly signed. So the sending number must also
12749
+ match a *verified* SMS notification target.
12750
+
12751
+ A reply may decide an approval only when its server-derived
12752
+ `risk_tier` is 1. Anything higher is answered with a link to confirm in
12753
+ the app; replying cannot decide it. When more than one approval is
12754
+ pending, a bare YES/NO is answered with a request for the reference code
12755
+ rather than applied to a guess.
12756
+
12757
+ Always answers 200 with TwiML — a non-2xx makes Twilio retry a message
12758
+ that was deliberately refused. The exception is a bad signature, which is
12759
+ 403, because a persistently failing signature is a misconfiguration as
12760
+ often as an attack and silence would hide both.
12761
+ operationId: smsWebhook
12762
+ security: []
12763
+ parameters:
12764
+ - name: webhook_path
12765
+ in: path
12766
+ required: true
12767
+ schema: { type: string }
12768
+ - name: X-Twilio-Signature
12769
+ in: header
12770
+ required: true
12771
+ schema: { type: string }
12772
+ requestBody:
12773
+ required: true
12774
+ content:
12775
+ application/x-www-form-urlencoded:
12776
+ schema:
12777
+ type: object
12778
+ properties:
12779
+ From: { type: string, example: "+14155550123" }
12780
+ To: { type: string, example: "+14155550999" }
12781
+ Body: { type: string, example: "YES A1" }
12782
+ responses:
12783
+ "200":
12784
+ description: TwiML response; `<Response/>` when there is nothing to reply
12785
+ content:
12786
+ application/xml:
12787
+ schema: { type: string }
12788
+ "403":
12789
+ $ref: "#/components/responses/Forbidden"
12790
+ "404":
12791
+ $ref: "#/components/responses/NotFound"
12792
+
12793
+ /v1/webhooks/discord/{webhook_path}:
12794
+ post:
12795
+ tags: [Agent Channels]
12796
+ summary: Discord webhook
12797
+ description: Public webhook endpoint for receiving Discord bot interactions.
12798
+ operationId: discordWebhook
12799
+ security: []
12800
+ parameters:
12801
+ - name: webhook_path
12802
+ in: path
12803
+ required: true
12804
+ schema:
12805
+ type: string
12806
+ requestBody:
12807
+ required: true
12808
+ content:
12809
+ application/json:
12810
+ schema:
12811
+ type: object
12812
+ responses:
12813
+ "200":
12814
+ description: Webhook processed
12815
+
12816
+ # ---------------------------------------------------------------------------
12817
+ # OAuth Connect
12818
+ # ---------------------------------------------------------------------------
12819
+
12820
+ /v1/oauth/providers:
12821
+ get:
12822
+ tags: [OAuth Connect]
12823
+ summary: List OAuth providers
12824
+ description: |
12825
+ Returns the list of supported OAuth providers with their metadata,
12826
+ available scopes, and authorization URLs. No authentication required.
12827
+ operationId: listOAuthProviders
12828
+ security: []
12829
+ responses:
12830
+ "200":
12831
+ description: Provider list
12832
+ content:
12833
+ application/json:
12834
+ schema:
12835
+ $ref: "#/components/schemas/OAuthProviderListResponse"
12836
+
12837
+ /v1/connectors/presets:
12838
+ get:
12839
+ tags: [Connectors]
12840
+ summary: List connector presets
12841
+ description: |
12842
+ The catalogue of pre-built connectors — Gmail, Slack, GitHub and the rest.
12843
+ Each preset carries the OAuth provider and scopes to request, plus the
12844
+ binding config and host/path guardrails the agent will execute under.
12845
+
12846
+ No authentication required: this describes what 1Claw supports, not
12847
+ anything belonging to an organisation.
12848
+ operationId: listConnectorPresets
12849
+ security: []
12850
+ responses:
12851
+ "200":
12852
+ description: Preset catalogue
12853
+ content:
12854
+ application/json:
12855
+ schema:
12856
+ type: object
12857
+ properties:
12858
+ presets:
12859
+ type: array
12860
+ items:
12861
+ $ref: "#/components/schemas/ConnectorPreset"
12862
+
12863
+ /v1/agents/{agent_id}/connectors:
12864
+ get:
12865
+ tags: [Connectors]
12866
+ summary: List installed connectors
12867
+ description: |
12868
+ Connectors installed on this agent, and whether each one has actually
12869
+ been connected — an install creates the binding, but the binding is not
12870
+ usable until the OAuth round trip completes.
12871
+ operationId: listInstalledConnectors
12872
+ parameters:
12873
+ - name: agent_id
12874
+ in: path
12875
+ required: true
12876
+ schema: { type: string, format: uuid }
12877
+ responses:
12878
+ "200":
12879
+ description: Installed connectors
12880
+ content:
12881
+ application/json:
12882
+ schema:
12883
+ type: object
12884
+ properties:
12885
+ connectors:
12886
+ type: array
12887
+ items:
12888
+ $ref: "#/components/schemas/InstalledConnector"
12889
+ "403":
12890
+ $ref: "#/components/responses/Forbidden"
12891
+ "404":
12892
+ $ref: "#/components/responses/NotFound"
12893
+
12894
+ /v1/agents/{agent_id}/connectors/{slug}/install:
12895
+ post:
12896
+ tags: [Connectors]
12897
+ summary: Install a connector
12898
+ description: |
12899
+ Creates a binding from the preset — base URL, allowed hosts, allowed
12900
+ paths — and starts the OAuth flow for it. Send the user to the returned
12901
+ `authorization_url` to finish; until they do, the binding exists but
12902
+ holds no credential.
12903
+
12904
+ Human users only. Installing gives an agent reach into a third-party
12905
+ account, and the flow it starts is a person's browser.
12906
+
12907
+ Idempotent by binding name: re-installing re-runs the OAuth flow against
12908
+ the existing binding rather than creating a second one holding a second
12909
+ token for the same account. A name already taken by a binding that is
12910
+ not this connector returns 409.
12911
+ operationId: installConnector
12912
+ parameters:
12913
+ - name: agent_id
12914
+ in: path
12915
+ required: true
12916
+ schema: { type: string, format: uuid }
12917
+ - name: slug
12918
+ in: path
12919
+ required: true
12920
+ description: Connector preset slug, e.g. `gmail`.
12921
+ schema: { type: string }
12922
+ requestBody:
12923
+ required: true
12924
+ content:
12925
+ application/json:
12926
+ schema:
12927
+ type: object
12928
+ properties:
12929
+ binding_name:
12930
+ type: string
12931
+ description: Defaults to the preset slug.
12932
+ scopes:
12933
+ type: array
12934
+ items: { type: string }
12935
+ description: |
12936
+ Narrow the preset's scopes. Widening is refused — the
12937
+ preset's scope list is the reviewed part of a one-click
12938
+ install. Must still include the preset's required scopes.
12939
+ redirect_after:
12940
+ type: string
12941
+ description: Where to send the user after the OAuth round trip.
12942
+ responses:
12943
+ "201":
12944
+ description: Connector installed; OAuth may still be pending
12945
+ content:
12946
+ application/json:
12947
+ schema:
12948
+ type: object
12949
+ required: [binding_id, binding_name, preset_slug, next_step]
12950
+ properties:
12951
+ binding_id: { type: string, format: uuid }
12952
+ binding_name: { type: string }
12953
+ preset_slug: { type: string }
12954
+ authorization_url:
12955
+ type: string
12956
+ nullable: true
12957
+ description: Absent for connectors that use a pasted API key rather than OAuth.
12958
+ next_step:
12959
+ type: string
12960
+ description: What the user still has to do, in words.
12961
+ "400":
12962
+ $ref: "#/components/responses/BadRequest"
12963
+ "403":
12964
+ $ref: "#/components/responses/Forbidden"
12965
+ "404":
12966
+ $ref: "#/components/responses/NotFound"
12967
+ "409":
12968
+ $ref: "#/components/responses/Conflict"
12969
+
12970
+ /v1/agents/{agent_id}/oauth/connect:
12971
+ post:
12972
+ tags: [OAuth Connect]
12973
+ summary: Initiate OAuth connection
12974
+ description: |
12975
+ Start an OAuth authorization flow for the specified agent and provider.
12976
+ Returns the authorization URL to redirect the user to. Human-only.
12977
+ operationId: connectOAuth
12978
+ parameters:
12979
+ - $ref: "#/components/parameters/AgentId"
12980
+ requestBody:
12981
+ required: true
12982
+ content:
12983
+ application/json:
12984
+ schema:
12985
+ $ref: "#/components/schemas/ConnectOAuthRequest"
12986
+ responses:
12987
+ "200":
12988
+ description: Authorization URL generated
12989
+ content:
12990
+ application/json:
12991
+ schema:
12992
+ $ref: "#/components/schemas/ConnectOAuthResponse"
12993
+ "400":
12994
+ $ref: "#/components/responses/BadRequest"
12995
+ "403":
12996
+ $ref: "#/components/responses/Forbidden"
12997
+ "404":
12998
+ $ref: "#/components/responses/NotFound"
12999
+
13000
+ /v1/agents/{agent_id}/oauth/connections:
13001
+ get:
13002
+ tags: [OAuth Connect]
13003
+ summary: List OAuth connections
13004
+ description: List all active OAuth connections for the specified agent.
11427
13005
  operationId: listOAuthConnections
11428
13006
  parameters:
11429
13007
  - $ref: "#/components/parameters/AgentId"
@@ -16041,6 +17619,22 @@ components:
16041
17619
  additionalProperties: true
16042
17620
  nullable: true
16043
17621
  description: Graduated transaction approval policy (HITL thresholds). Separate from hard guardrails.
17622
+ action_approval_policy:
17623
+ type: object
17624
+ additionalProperties: true
17625
+ nullable: true
17626
+ description: |
17627
+ Which business actions this agent must ask a human about, and above what
17628
+ amount. `{}` means no per-action rules.
17629
+
17630
+ Shape: `{ "default_mode": "deny|approve|allow", "rules": [ { "action_type":
17631
+ "refund.create", "mode": "approve", "require_for_amount_above_usd": "50",
17632
+ "summary_template": "Refund {{amount_usd}} to {{customer_email}}" } ] }`.
17633
+
17634
+ A rule can only raise the bar. Editing this is classified as a guardrail
17635
+ widening, so it routes through the same approval flow as loosening a
17636
+ transaction limit. Malformed rules are rejected on write rather than
17637
+ ignored at request time.
16044
17638
  typed_data_policy:
16045
17639
  type: string
16046
17640
  enum: [deny, approve]
@@ -16223,6 +17817,22 @@ components:
16223
17817
  additionalProperties: true
16224
17818
  nullable: true
16225
17819
  description: Graduated transaction approval policy (HITL thresholds).
17820
+ action_approval_policy:
17821
+ type: object
17822
+ additionalProperties: true
17823
+ nullable: true
17824
+ description: |
17825
+ Which business actions this agent must ask a human about, and above what
17826
+ amount. `{}` means no per-action rules.
17827
+
17828
+ Shape: `{ "default_mode": "deny|approve|allow", "rules": [ { "action_type":
17829
+ "refund.create", "mode": "approve", "require_for_amount_above_usd": "50",
17830
+ "summary_template": "Refund {{amount_usd}} to {{customer_email}}" } ] }`.
17831
+
17832
+ A rule can only raise the bar. Editing this is classified as a guardrail
17833
+ widening, so it routes through the same approval flow as loosening a
17834
+ transaction limit. Malformed rules are rejected on write rather than
17835
+ ignored at request time.
16226
17836
  typed_data_policy:
16227
17837
  type: string
16228
17838
  enum: [deny, approve]
@@ -16494,6 +18104,22 @@ components:
16494
18104
  additionalProperties: true
16495
18105
  nullable: true
16496
18106
  description: Graduated transaction approval policy (HITL thresholds).
18107
+ action_approval_policy:
18108
+ type: object
18109
+ additionalProperties: true
18110
+ nullable: true
18111
+ description: |
18112
+ Which business actions this agent must ask a human about, and above what
18113
+ amount. `{}` means no per-action rules.
18114
+
18115
+ Shape: `{ "default_mode": "deny|approve|allow", "rules": [ { "action_type":
18116
+ "refund.create", "mode": "approve", "require_for_amount_above_usd": "50",
18117
+ "summary_template": "Refund {{amount_usd}} to {{customer_email}}" } ] }`.
18118
+
18119
+ A rule can only raise the bar. Editing this is classified as a guardrail
18120
+ widening, so it routes through the same approval flow as loosening a
18121
+ transaction limit. Malformed rules are rejected on write rather than
18122
+ ignored at request time.
16497
18123
  typed_data_policy:
16498
18124
  type: string
16499
18125
  enum: [deny, approve]
@@ -19969,6 +21595,140 @@ components:
19969
21595
  type: string
19970
21596
  format: date-time
19971
21597
 
21598
+ FleetSummaryResponse:
21599
+ type: object
21600
+ properties:
21601
+ template_id:
21602
+ type: string
21603
+ format: uuid
21604
+ template_name:
21605
+ type: string
21606
+ current_version:
21607
+ type: integer
21608
+ spec_hash:
21609
+ type: [string, "null"]
21610
+ description: >
21611
+ SHA-256 of the template spec. Lets a caller tell a version bump that
21612
+ changed nothing from one that did. Null on templates written before
21613
+ migration 245.
21614
+ total_agents:
21615
+ type: integer
21616
+ format: int64
21617
+ version_skew:
21618
+ type: array
21619
+ description: How the cohort splits across the versions it was provisioned from.
21620
+ items:
21621
+ type: object
21622
+ properties:
21623
+ template_version:
21624
+ type: [integer, "null"]
21625
+ agents:
21626
+ type: integer
21627
+ format: int64
21628
+ agents_on_current_version:
21629
+ type: integer
21630
+ format: int64
21631
+ agents_behind:
21632
+ type: integer
21633
+ format: int64
21634
+ drifted_agents:
21635
+ type: integer
21636
+ format: int64
21637
+ description: Agents a previous rollout declined to touch.
21638
+ bulk_patchable_fields:
21639
+ type: array
21640
+ description: >
21641
+ The fields bulk-patch and rollout will carry. Read this rather than
21642
+ hard-coding the list; it is deliberately narrower than a single-agent
21643
+ PATCH and may narrow further.
21644
+ items:
21645
+ type: string
21646
+
21647
+ ListFleetAgentsResponse:
21648
+ type: object
21649
+ properties:
21650
+ agents:
21651
+ type: array
21652
+ items:
21653
+ $ref: "#/components/schemas/FleetAgent"
21654
+ limit:
21655
+ type: integer
21656
+ offset:
21657
+ type: integer
21658
+ current_version:
21659
+ type: integer
21660
+
21661
+ FleetAgent:
21662
+ type: object
21663
+ properties:
21664
+ agent_id:
21665
+ type: string
21666
+ format: uuid
21667
+ name:
21668
+ type: string
21669
+ org_id:
21670
+ type: string
21671
+ format: uuid
21672
+ platform_connection_id:
21673
+ type: [string, "null"]
21674
+ format: uuid
21675
+ provisioned_from_version:
21676
+ type: [integer, "null"]
21677
+ last_fleet_sync_at:
21678
+ type: [string, "null"]
21679
+ format: date-time
21680
+ drift_fields:
21681
+ type: array
21682
+ description: >
21683
+ Fields a rollout skipped because they were changed outside fleet
21684
+ control. The standing answer to "why is this agent behind?".
21685
+ items:
21686
+ type: string
21687
+ is_active:
21688
+ type: boolean
21689
+ is_current:
21690
+ type: boolean
21691
+
21692
+ FleetRolloutResponse:
21693
+ type: object
21694
+ properties:
21695
+ job_id:
21696
+ type: [string, "null"]
21697
+ format: uuid
21698
+ description: Null for a dry run, which claims no job.
21699
+ to_version:
21700
+ type: integer
21701
+ dry_run:
21702
+ type: boolean
21703
+ forced:
21704
+ type: boolean
21705
+ total_agents:
21706
+ type: integer
21707
+ format: int64
21708
+ synced:
21709
+ type: integer
21710
+ already_current:
21711
+ type: integer
21712
+ skipped_drifted:
21713
+ type: integer
21714
+ outcomes:
21715
+ type: array
21716
+ items:
21717
+ type: object
21718
+ properties:
21719
+ outcome:
21720
+ type: string
21721
+ enum: [already_current, synced, skipped_drifted]
21722
+ agent_id:
21723
+ type: string
21724
+ format: uuid
21725
+ fields:
21726
+ type: array
21727
+ items: { type: string }
21728
+ drift_fields:
21729
+ type: array
21730
+ items: { type: string }
21731
+
19972
21732
  UpsertPlatformUserRequest:
19973
21733
  type: object
19974
21734
  properties:
@@ -20531,6 +22291,170 @@ components:
20531
22291
  type: string
20532
22292
  description: Optional human-readable reason for the decision
20533
22293
 
22294
+ UsageCounts:
22295
+ type: object
22296
+ required: [api_requests, signatures, execution_intents, execution_intents_tee, inference_usd, credits_debited_cents]
22297
+ properties:
22298
+ api_requests: { type: integer, format: int64 }
22299
+ signatures: { type: integer, format: int64 }
22300
+ execution_intents: { type: integer, format: int64 }
22301
+ execution_intents_tee: { type: integer, format: int64 }
22302
+ inference_usd:
22303
+ type: string
22304
+ description: A decimal string. Money is not a float; zero is "0".
22305
+ example: "3.42"
22306
+ credits_debited_cents: { type: integer, format: int64 }
22307
+
22308
+ AppUsageReport:
22309
+ type: object
22310
+ required: [app_id, period_start, period_end, connections, unattributed, totals, has_ambiguous_usage]
22311
+ properties:
22312
+ app_id: { type: string, format: uuid }
22313
+ period_start: { type: string, format: date-time }
22314
+ period_end:
22315
+ type: string
22316
+ format: date-time
22317
+ description: Exclusive. The period is half-open, so an event at midnight belongs to one month, not two.
22318
+ connections:
22319
+ type: array
22320
+ items:
22321
+ type: object
22322
+ required: [connection_id, usage]
22323
+ properties:
22324
+ connection_id: { type: string, format: uuid }
22325
+ usage: { $ref: "#/components/schemas/UsageCounts" }
22326
+ unattributed:
22327
+ type: object
22328
+ description: Usage that could not be charged to a connection.
22329
+ properties:
22330
+ ambiguous:
22331
+ allOf:
22332
+ - $ref: "#/components/schemas/UsageCounts"
22333
+ description: The agent belongs to several connections and none was named. This belongs to someone.
22334
+ none:
22335
+ allOf:
22336
+ - $ref: "#/components/schemas/UsageCounts"
22337
+ description: No platform linkage. Normal for most traffic.
22338
+ totals:
22339
+ allOf:
22340
+ - $ref: "#/components/schemas/UsageCounts"
22341
+ description: Connections plus both unattributed buckets. Derived, not queried.
22342
+ has_ambiguous_usage:
22343
+ type: boolean
22344
+ description: Some usage this period belongs to an end-user who cannot be identified.
22345
+
22346
+ Peer:
22347
+ type: object
22348
+ required: [id, peer_type, peer_ref, profile, status, observer_count, created_at]
22349
+ properties:
22350
+ id: { type: string, format: uuid }
22351
+ peer_type: { type: string, enum: [user, platform_connection, external] }
22352
+ peer_ref: { type: string }
22353
+ display_name: { type: string, nullable: true }
22354
+ profile: { type: object, additionalProperties: true }
22355
+ status:
22356
+ type: string
22357
+ enum: [active, archived]
22358
+ description: |
22359
+ Archived when a connection is disconnected. Agents lose observation;
22360
+ the person keeps export and delete.
22361
+ observer_count:
22362
+ type: integer
22363
+ description: How many agents observe this peer. The list itself is not returned here.
22364
+ created_at: { type: string, format: date-time }
22365
+
22366
+ PeerFact:
22367
+ type: object
22368
+ required: [fact_key, fact_value, provenance, edited_by_human, updated_at]
22369
+ properties:
22370
+ fact_key: { type: string, example: "approval_tendency:refund.create|0-10|known|a@b.co" }
22371
+ fact_value: { type: object, additionalProperties: true }
22372
+ confidence:
22373
+ type: string
22374
+ nullable: true
22375
+ description: 0..1, capped below certainty — no history makes the next decision certain.
22376
+ provenance:
22377
+ type: array
22378
+ items: { type: object }
22379
+ description: |
22380
+ Why this is believed. Entries are `event`, `tombstone` (the event has
22381
+ expired; kind and decision are kept, content is not) or `human`.
22382
+ edited_by_human:
22383
+ type: boolean
22384
+ description: A person corrected this. The processor will not overwrite it.
22385
+ updated_at: { type: string, format: date-time }
22386
+
22387
+ NotificationTarget:
22388
+ type: object
22389
+ required: [id, target_type, config, events, is_active, verified, created_at]
22390
+ properties:
22391
+ id: { type: string, format: uuid }
22392
+ target_type:
22393
+ type: string
22394
+ enum: [sms, webhook, expo, email]
22395
+ user_id: { type: string, format: uuid, nullable: true }
22396
+ agent_id: { type: string, format: uuid, nullable: true }
22397
+ config: { type: object, additionalProperties: true }
22398
+ events:
22399
+ type: array
22400
+ items: { type: string }
22401
+ is_active: { type: boolean }
22402
+ verified:
22403
+ type: boolean
22404
+ description: |
22405
+ An unverified SMS target receives notifications but cannot decide
22406
+ an approval by reply.
22407
+ created_at: { type: string, format: date-time }
22408
+
22409
+ ConnectorPreset:
22410
+ type: object
22411
+ required: [slug, display_name, description, category, binding_type, base_url, requires_oauth]
22412
+ properties:
22413
+ slug: { type: string, example: gmail }
22414
+ display_name: { type: string, example: Gmail }
22415
+ description: { type: string }
22416
+ category: { type: string, example: communication }
22417
+ provider_slug:
22418
+ type: string
22419
+ nullable: true
22420
+ description: "`oauth_providers.slug`, or null when the connector uses a pasted API key."
22421
+ oauth_scopes:
22422
+ type: array
22423
+ items: { type: string }
22424
+ required_scopes:
22425
+ type: array
22426
+ items: { type: string }
22427
+ description: Scopes without which the connector cannot do anything.
22428
+ binding_type: { type: string, example: http }
22429
+ base_url: { type: string, format: uri }
22430
+ allowed_hosts:
22431
+ type: array
22432
+ items: { type: string }
22433
+ description: Hosts the installed binding may reach. Always includes the base URL's host.
22434
+ documentation_url: { type: string, format: uri }
22435
+ tier_required: { type: string, example: free }
22436
+ requires_oauth: { type: boolean }
22437
+
22438
+ InstalledConnector:
22439
+ type: object
22440
+ required: [binding_id, binding_name, preset_slug, is_active, connected, needs_reauth, created_at]
22441
+ properties:
22442
+ binding_id: { type: string, format: uuid }
22443
+ binding_name: { type: string }
22444
+ preset_slug: { type: string }
22445
+ display_name:
22446
+ type: string
22447
+ nullable: true
22448
+ description: Null if the preset has since been retired from the catalogue.
22449
+ is_active: { type: boolean }
22450
+ connected:
22451
+ type: boolean
22452
+ description: The OAuth round trip completed and a token is stored.
22453
+ needs_reauth:
22454
+ type: boolean
22455
+ description: The stored token was rejected; the user must reconnect.
22456
+ created_at: { type: string, format: date-time }
22457
+
20534
22458
  ApprovalResponse:
20535
22459
  type: object
20536
22460
  required: [id, org_id, user_id, action, target_type, target_id, risk_tier, status, summary, created_at]
@@ -20558,6 +22482,23 @@ components:
20558
22482
  type: integer
20559
22483
  minimum: 1
20560
22484
  maximum: 3
22485
+ description: "The tier actually enforced. Authoritative."
22486
+ declared_risk_tier:
22487
+ type: integer
22488
+ minimum: 1
22489
+ maximum: 3
22490
+ nullable: true
22491
+ description: "What the caller asked for, when it asked for anything."
22492
+ declared_below_floor:
22493
+ type: boolean
22494
+ description: "The caller asked for a lower tier than policy required."
22495
+ human_summary:
22496
+ type: string
22497
+ nullable: true
22498
+ description: "Plain-language line sent to SMS, push and email."
22499
+ payload:
22500
+ type: object
22501
+ description: "What the action will do, as submitted."
20561
22502
  status:
20562
22503
  type: string
20563
22504
  enum: [pending, approved, rejected, expired]
@@ -20912,7 +22853,7 @@ components:
20912
22853
 
20913
22854
  ConnectionUsageResponse:
20914
22855
  type: object
20915
- required: [connection_id, period, inference_spent_usd]
22856
+ required: [connection_id, period, inference_spent_usd, usage, period_start, period_end]
20916
22857
  properties:
20917
22858
  connection_id:
20918
22859
  type: string
@@ -20922,6 +22863,23 @@ components:
20922
22863
  description: UTC month (YYYY-MM)
20923
22864
  inference_spent_usd:
20924
22865
  type: string
22866
+ description: |
22867
+ Kept for compatibility — this field predates the breakdown below and
22868
+ existing integrations read it. Same number as `usage.inference_usd`.
22869
+ usage:
22870
+ allOf:
22871
+ - $ref: "#/components/schemas/UsageCounts"
22872
+ description: |
22873
+ Everything billable for this connection in the period. Derived from the
22874
+ same grouped query as the app report, so a connection can never report a
22875
+ number the app report disagrees with.
22876
+ period_start:
22877
+ type: string
22878
+ format: date-time
22879
+ period_end:
22880
+ type: string
22881
+ format: date-time
22882
+ description: Exclusive. The period is half-open.
20925
22883
 
20926
22884
  EntitlementsListResponse:
20927
22885
  type: object