@1claw/openapi-spec 0.59.9 → 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 +3532 -312
  3. package/openapi.yaml +2193 -63
  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.9"
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
@@ -1649,7 +1685,17 @@ paths:
1649
1685
  schema:
1650
1686
  $ref: "#/components/schemas/VaultResponse"
1651
1687
  "400":
1652
- $ref: "#/components/responses/BadRequest"
1688
+ description: The name is empty or longer than 255 characters. Checked before the consensus gate and the quota, so a malformed request consumes neither an approval nor a rate-limit slot.
1689
+ content:
1690
+ application/json:
1691
+ schema:
1692
+ $ref: "#/components/schemas/ProblemDetails"
1693
+ "409":
1694
+ description: A vault with that name already exists in this organization. Names are unique per org; this previously surfaced as a 500.
1695
+ content:
1696
+ application/json:
1697
+ schema:
1698
+ $ref: "#/components/schemas/ProblemDetails"
1653
1699
  get:
1654
1700
  tags: [Vaults]
1655
1701
  summary: List vaults
@@ -6652,6 +6698,224 @@ paths:
6652
6698
  "404":
6653
6699
  $ref: "#/components/responses/NotFound"
6654
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"
6655
6919
  /v1/platform/apps/{appId}/templates/{template_id}/preview:
6656
6920
  post:
6657
6921
  tags: [Platform]
@@ -7504,6 +7768,26 @@ paths:
7504
7768
  "404":
7505
7769
  $ref: "#/components/responses/NotFound"
7506
7770
 
7771
+ post:
7772
+ tags: [Platform]
7773
+ summary: Create an approval on a platform connection
7774
+ operationId: createConnectionApproval
7775
+ security:
7776
+ - BearerAuth: []
7777
+ parameters:
7778
+ - in: path
7779
+ name: connectionId
7780
+ required: true
7781
+ schema:
7782
+ type: string
7783
+ format: uuid
7784
+ responses:
7785
+ "201":
7786
+ description: Approval created
7787
+ "403":
7788
+ $ref: "#/components/responses/Forbidden"
7789
+ "404":
7790
+ $ref: "#/components/responses/NotFound"
7507
7791
  /v1/platform/connections/{connectionId}/approvals/{approvalId}:
7508
7792
  get:
7509
7793
  tags: [Platform]
@@ -7896,6 +8180,32 @@ paths:
7896
8180
  "404":
7897
8181
  $ref: "#/components/responses/NotFound"
7898
8182
 
8183
+ post:
8184
+ tags: [Platform]
8185
+ summary: "Alias for PATCH on this path — same handler, same limited settings. Present because some clients cannot send PATCH."
8186
+ operationId: postConnectionAgent
8187
+ security:
8188
+ - BearerAuth: []
8189
+ parameters:
8190
+ - in: path
8191
+ name: connectionId
8192
+ required: true
8193
+ schema:
8194
+ type: string
8195
+ format: uuid
8196
+ - in: path
8197
+ name: agentId
8198
+ required: true
8199
+ schema:
8200
+ type: string
8201
+ format: uuid
8202
+ responses:
8203
+ "200":
8204
+ description: Agent updated
8205
+ "403":
8206
+ $ref: "#/components/responses/Forbidden"
8207
+ "404":
8208
+ $ref: "#/components/responses/NotFound"
7899
8209
  /v1/platform/connections/{connectionId}/portfolio:
7900
8210
  get:
7901
8211
  tags: [Platform]
@@ -8713,6 +9023,30 @@ paths:
8713
9023
  "409":
8714
9024
  $ref: "#/components/responses/Conflict"
8715
9025
 
9026
+ post:
9027
+ tags: [Approvals]
9028
+ summary: "One-click approve or deny, as an API call. The GET on this path is the preview an email link opens; this is the decision itself."
9029
+ operationId: quickDecide
9030
+ requestBody:
9031
+ required: true
9032
+ content:
9033
+ application/json:
9034
+ schema:
9035
+ type: object
9036
+ required: [token, decision]
9037
+ properties:
9038
+ token:
9039
+ type: string
9040
+ decision:
9041
+ type: string
9042
+ enum: [approve, deny]
9043
+ responses:
9044
+ "200":
9045
+ description: Decision recorded
9046
+ "400":
9047
+ description: Malformed or expired token
9048
+ "404":
9049
+ $ref: "#/components/responses/NotFound"
8716
9050
  /v1/deposit-destinations:
8717
9051
  post:
8718
9052
  tags: [Treasury]
@@ -10516,6 +10850,17 @@ paths:
10516
10850
  $ref: "#/components/responses/Forbidden"
10517
10851
  "404":
10518
10852
  $ref: "#/components/responses/NotFound"
10853
+ "503":
10854
+ description: |
10855
+ The runtime's last start failed less than 120 seconds ago and is not
10856
+ being retried yet. Chat starts a stopped runtime, so a client that
10857
+ retries on failure turns every attempt into another deploy — one
10858
+ polling client did exactly that every 8 seconds for days. Wait out
10859
+ the cooldown, or call the start endpoint to see the underlying error.
10860
+ content:
10861
+ application/json:
10862
+ schema:
10863
+ $ref: "#/components/schemas/ProblemDetails"
10519
10864
 
10520
10865
  # ---------------------------------------------------------------------------
10521
10866
  # Agent Memory
@@ -10563,12 +10908,10 @@ paths:
10563
10908
  $ref: "#/components/responses/Unauthorized"
10564
10909
  "404":
10565
10910
  $ref: "#/components/responses/NotFound"
10566
-
10567
- /v1/agents/{agent_id}/memory/{namespace}/{key}:
10568
- put:
10911
+ delete:
10569
10912
  tags: [Agent Memory]
10570
- summary: Put memory entry
10571
- operationId: putMemoryEntry
10913
+ summary: "Delete a memory namespace and every entry in it. Destructive and not reversible; there is no per-entry confirmation."
10914
+ operationId: deleteMemoryNamespace
10572
10915
  parameters:
10573
10916
  - $ref: "#/components/parameters/AgentId"
10574
10917
  - name: namespace
@@ -10576,10 +10919,30 @@ paths:
10576
10919
  required: true
10577
10920
  schema:
10578
10921
  type: string
10579
- - name: key
10580
- in: path
10581
- required: true
10582
- schema:
10922
+ responses:
10923
+ "204":
10924
+ description: Namespace deleted
10925
+ "401":
10926
+ $ref: "#/components/responses/Unauthorized"
10927
+ "404":
10928
+ $ref: "#/components/responses/NotFound"
10929
+
10930
+ /v1/agents/{agent_id}/memory/{namespace}/{key}:
10931
+ put:
10932
+ tags: [Agent Memory]
10933
+ summary: Put memory entry
10934
+ operationId: putMemoryEntry
10935
+ parameters:
10936
+ - $ref: "#/components/parameters/AgentId"
10937
+ - name: namespace
10938
+ in: path
10939
+ required: true
10940
+ schema:
10941
+ type: string
10942
+ - name: key
10943
+ in: path
10944
+ required: true
10945
+ schema:
10583
10946
  type: string
10584
10947
  requestBody:
10585
10948
  required: true
@@ -11236,49 +11599,1373 @@ paths:
11236
11599
  "200":
11237
11600
  description: Webhook processed
11238
11601
 
11239
- /v1/webhooks/discord/{webhook_path}:
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
11626
+ parameters:
11627
+ - name: app_id
11628
+ in: path
11629
+ required: true
11630
+ schema: { type: string, format: uuid }
11631
+ responses:
11632
+ "200":
11633
+ description: Usage report
11634
+ content:
11635
+ application/json:
11636
+ schema:
11637
+ $ref: "#/components/schemas/AppUsageReport"
11638
+ "404":
11639
+ $ref: "#/components/responses/NotFound"
11640
+
11641
+ /v1/platform/apps/{app_id}/usage/export:
11642
+ get:
11643
+ tags: [Platform API]
11644
+ summary: Usage as CSV for billing reconciliation
11645
+ description: |
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 }
11655
+ responses:
11656
+ "200":
11657
+ description: CSV
11658
+ content:
11659
+ text/csv:
11660
+ schema: { type: string }
11661
+ "404":
11662
+ $ref: "#/components/responses/NotFound"
11663
+
11664
+ /v1/org/apply:
11240
11665
  post:
11241
- tags: [Agent Channels]
11242
- summary: Discord webhook
11243
- description: Public webhook endpoint for receiving Discord bot interactions.
11244
- operationId: discordWebhook
11666
+ tags: [Organization]
11667
+ summary: Apply a chart
11668
+ description: |
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
11693
+ requestBody:
11694
+ required: true
11695
+ content:
11696
+ application/json:
11697
+ schema:
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
11707
+ responses:
11708
+ "200":
11709
+ description: What happened to each resource
11710
+ content:
11711
+ application/json:
11712
+ schema:
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.
11738
+ "400":
11739
+ $ref: "#/components/responses/BadRequest"
11740
+ "403":
11741
+ $ref: "#/components/responses/Forbidden"
11742
+
11743
+ /v1/org/approval-learning/shadow-report:
11744
+ get:
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
11245
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
11246
11891
  parameters:
11247
- - name: webhook_path
11892
+ - name: agent_id
11248
11893
  in: path
11249
11894
  required: true
11250
- schema:
11251
- type: string
11895
+ schema: { type: string, format: uuid }
11252
11896
  requestBody:
11253
11897
  required: true
11254
11898
  content:
11255
11899
  application/json:
11256
11900
  schema:
11257
11901
  type: object
11902
+ required: [preset]
11903
+ properties:
11904
+ preset: { type: string, example: small-business-spender }
11258
11905
  responses:
11259
11906
  "200":
11260
- description: Webhook processed
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"
11261
11927
 
11262
- # ---------------------------------------------------------------------------
11263
- # OAuth Connect
11264
- # ---------------------------------------------------------------------------
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.
11265
11936
 
11266
- /v1/oauth/providers:
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:
11267
11968
  get:
11268
- tags: [OAuth Connect]
11269
- summary: List OAuth providers
11969
+ tags: [Discovery]
11970
+ summary: What a listed agent has earned
11270
11971
  description: |
11271
- Returns the list of supported OAuth providers with their metadata,
11272
- available scopes, and authorization URLs. No authentication required.
11273
- operationId: listOAuthProviders
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
11274
11984
  security: []
11985
+ parameters:
11986
+ - name: agent_id
11987
+ in: path
11988
+ required: true
11989
+ schema: { type: string, format: uuid }
11275
11990
  responses:
11276
11991
  "200":
11277
- description: Provider list
11992
+ description: Trust signals
11278
11993
  content:
11279
11994
  application/json:
11280
11995
  schema:
11281
- $ref: "#/components/schemas/OAuthProviderListResponse"
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"
11282
12969
 
11283
12970
  /v1/agents/{agent_id}/oauth/connect:
11284
12971
  post:
@@ -12656,30 +14343,6 @@ paths:
12656
14343
  "404":
12657
14344
  $ref: "#/components/responses/NotFound"
12658
14345
 
12659
- /v1/auth/credential-recovery/requests/{id}:
12660
- delete:
12661
- tags: [Credential Recovery]
12662
- summary: Cancel or reject a recovery request
12663
- operationId: cancelCredentialRecovery
12664
- security:
12665
- - BearerAuth: []
12666
- parameters:
12667
- - name: id
12668
- in: path
12669
- required: true
12670
- schema:
12671
- type: string
12672
- format: uuid
12673
- responses:
12674
- "204":
12675
- description: Request cancelled
12676
- "401":
12677
- $ref: "#/components/responses/Unauthorized"
12678
- "403":
12679
- $ref: "#/components/responses/Forbidden"
12680
- "404":
12681
- $ref: "#/components/responses/NotFound"
12682
-
12683
14346
  # ---------------------------------------------------------------------------
12684
14347
  # Org Credential Recovery Policy
12685
14348
  # ---------------------------------------------------------------------------
@@ -14241,6 +15904,67 @@ paths:
14241
15904
  description: Not a human caller — agents and platform keys cannot pair a device
14242
15905
  '409':
14243
15906
  description: That label is already pinned to a different key
15907
+ get:
15908
+ tags: [Browser Bridge]
15909
+ summary: "List the browser bridges paired to this organization. Revoked devices are included: 'was this machine ever paired' is the question asked after a laptop goes missing."
15910
+ operationId: list_browser_devices
15911
+ responses:
15912
+ '200':
15913
+ description: The paired devices. No credential material is returned — the row holds an Argon2 hash and a prefix, and neither is exposed.
15914
+ content:
15915
+ application/json:
15916
+ schema:
15917
+ type: object
15918
+ properties:
15919
+ devices:
15920
+ type: array
15921
+ items:
15922
+ type: object
15923
+ properties:
15924
+ id:
15925
+ type: string
15926
+ format: uuid
15927
+ label:
15928
+ type: string
15929
+ platform:
15930
+ type: string
15931
+ nullable: true
15932
+ bridge_version:
15933
+ type: string
15934
+ nullable: true
15935
+ last_seen_at:
15936
+ type: string
15937
+ format: date-time
15938
+ nullable: true
15939
+ revoked_at:
15940
+ type: string
15941
+ format: date-time
15942
+ nullable: true
15943
+ description: Set once revoked. A revoked credential stops resolving.
15944
+ created_at:
15945
+ type: string
15946
+ format: date-time
15947
+ '401':
15948
+ description: Unauthenticated
15949
+ /v1/browser/devices/{id}:
15950
+ delete:
15951
+ tags: [Browser Bridge]
15952
+ summary: "Revoke a paired bridge. This is what makes a leaked bb_ credential stop working."
15953
+ operationId: revoke_browser_device
15954
+ parameters:
15955
+ - name: id
15956
+ in: path
15957
+ required: true
15958
+ schema:
15959
+ type: string
15960
+ format: uuid
15961
+ responses:
15962
+ '204':
15963
+ description: Revoked. Subsequent use of that credential resolves to nothing.
15964
+ '401':
15965
+ description: Unauthenticated
15966
+ '404':
15967
+ description: No such device in this organization
14244
15968
  /v1/browser/credentials:
14245
15969
  post:
14246
15970
  tags: [Browser Bridge]
@@ -14271,7 +15995,7 @@ paths:
14271
15995
  minItems: 1
14272
15996
  items:
14273
15997
  type: string
14274
- description: "Bare hostnames, compared exactly. URLs, ports and wildcards are refused: a wildcard would match nothing while looking like it allowed something."
15998
+ description: "Hostnames. A bare entry matches only itself; a leading dot ('.example.com') matches that host and any subdomain. URLs, ports and '*' are refused '*' has no meaning to the matcher, so it would be stored and then match nothing."
14275
15999
  sso_hosts:
14276
16000
  type: array
14277
16001
  items:
@@ -14407,7 +16131,13 @@ paths:
14407
16131
  application/json:
14408
16132
  schema:
14409
16133
  type: object
14410
- required: [session_id, binding_id, tab_origin, frame_origin, frame_id, generation]
16134
+ # form_path, field_names, redirect_chain and current_generation are
16135
+ # required, not optional. They were accepted as absent and defaulted
16136
+ # server-side, which turned three of the policy's checks off: the
16137
+ # redirect loop never ran, current_generation was compared against
16138
+ # itself, and form_path defaulted to "", matching no fingerprint
16139
+ # pattern and denying every binding that carried one.
16140
+ required: [session_id, binding_id, tab_origin, frame_origin, frame_id, generation, form_path, field_names, redirect_chain, current_generation]
14411
16141
  properties:
14412
16142
  session_id:
14413
16143
  type: string
@@ -14425,7 +16155,24 @@ paths:
14425
16155
  description: "The bridge's navigation counter. Compared again at consume: a navigation in between means the page decided about is no longer the page in front of the bridge."
14426
16156
  form_action_origin:
14427
16157
  type: string
14428
- description: "Where the form would POST. Checked as well as the two origins — a login form on an allowed page can still submit to somebody else's host."
16158
+ description: "Where the form would POST. Checked as well as the two origins — a login form on an allowed page can still submit to somebody else's host. Absent is not treated as 'same as the tab'; it denies."
16159
+ form_path:
16160
+ type: string
16161
+ description: Path of the form being filled, checked against the binding's fingerprint.
16162
+ field_names:
16163
+ type: array
16164
+ items:
16165
+ type: string
16166
+ description: "Field names on that form. Every field the fingerprint expects must be present; extra fields are fine, since sites add hidden inputs."
16167
+ redirect_chain:
16168
+ type: array
16169
+ items:
16170
+ type: string
16171
+ description: "Hosts the login has redirected through, in order. Each is checked against the binding's allowed hosts union its sso hosts."
16172
+ current_generation:
16173
+ type: integer
16174
+ format: int64
16175
+ description: The target's generation now. A mismatch with `generation` means the page moved and the fill is denied.
14429
16176
  tab_origin:
14430
16177
  type: string
14431
16178
  description: "Origin of the tab being driven. Checked against the binding's allowed and sso hosts by exact host match."
@@ -15270,6 +17017,9 @@ components:
15270
17017
  properties:
15271
17018
  name:
15272
17019
  type: string
17020
+ minLength: 1
17021
+ maxLength: 255
17022
+ description: Unique within the organization. Trimmed before it is stored, and the trimmed value is what must be unique. Counted in characters, not bytes.
15273
17023
  description:
15274
17024
  type: string
15275
17025
  mpc_custody:
@@ -15869,6 +17619,22 @@ components:
15869
17619
  additionalProperties: true
15870
17620
  nullable: true
15871
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.
15872
17638
  typed_data_policy:
15873
17639
  type: string
15874
17640
  enum: [deny, approve]
@@ -16051,6 +17817,22 @@ components:
16051
17817
  additionalProperties: true
16052
17818
  nullable: true
16053
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.
16054
17836
  typed_data_policy:
16055
17837
  type: string
16056
17838
  enum: [deny, approve]
@@ -16322,6 +18104,22 @@ components:
16322
18104
  additionalProperties: true
16323
18105
  nullable: true
16324
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.
16325
18123
  typed_data_policy:
16326
18124
  type: string
16327
18125
  enum: [deny, approve]
@@ -19797,6 +21595,140 @@ components:
19797
21595
  type: string
19798
21596
  format: date-time
19799
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
+
19800
21732
  UpsertPlatformUserRequest:
19801
21733
  type: object
19802
21734
  properties:
@@ -20359,6 +22291,170 @@ components:
20359
22291
  type: string
20360
22292
  description: Optional human-readable reason for the decision
20361
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
+
20362
22458
  ApprovalResponse:
20363
22459
  type: object
20364
22460
  required: [id, org_id, user_id, action, target_type, target_id, risk_tier, status, summary, created_at]
@@ -20386,6 +22482,23 @@ components:
20386
22482
  type: integer
20387
22483
  minimum: 1
20388
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."
20389
22502
  status:
20390
22503
  type: string
20391
22504
  enum: [pending, approved, rejected, expired]
@@ -20740,7 +22853,7 @@ components:
20740
22853
 
20741
22854
  ConnectionUsageResponse:
20742
22855
  type: object
20743
- required: [connection_id, period, inference_spent_usd]
22856
+ required: [connection_id, period, inference_spent_usd, usage, period_start, period_end]
20744
22857
  properties:
20745
22858
  connection_id:
20746
22859
  type: string
@@ -20750,6 +22863,23 @@ components:
20750
22863
  description: UTC month (YYYY-MM)
20751
22864
  inference_spent_usd:
20752
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.
20753
22883
 
20754
22884
  EntitlementsListResponse:
20755
22885
  type: object