tripwire-server 0.3.1 → 0.3.2

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 (27) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +27 -7
  3. data/lib/tripwire/server/client.rb +75 -23
  4. data/lib/tripwire/server/gate_delivery.rb +27 -2
  5. data/lib/tripwire/server/version.rb +1 -1
  6. data/spec/README.md +4 -4
  7. data/spec/fixtures/api/gate/service-create.json +2 -2
  8. data/spec/fixtures/api/gate/service-detail.json +2 -2
  9. data/spec/fixtures/api/gate/service-disable.json +2 -2
  10. data/spec/fixtures/api/gate/service-update.json +2 -2
  11. data/spec/fixtures/api/gate/services-list.json +2 -2
  12. data/spec/fixtures/api/organizations/api-key-create.json +27 -0
  13. data/spec/fixtures/api/{teams → organizations}/api-key-list.json +10 -5
  14. data/spec/fixtures/api/{teams → organizations}/api-key-revoke.json +10 -5
  15. data/spec/fixtures/api/organizations/api-key-rotate.json +27 -0
  16. data/spec/fixtures/api/organizations/api-key-update.json +29 -0
  17. data/spec/fixtures/api/{teams/team-create.json → organizations/organization-create.json} +4 -4
  18. data/spec/fixtures/api/{teams/team-update.json → organizations/organization-update.json} +4 -4
  19. data/spec/fixtures/api/{teams/team.json → organizations/organization.json} +4 -4
  20. data/spec/fixtures/api/sessions/detail.json +45 -35
  21. data/spec/fixtures/gate-delivery/approved-webhook-payload.valid.json +0 -1
  22. data/spec/fixtures/gate-delivery/webhook-signature.json +3 -3
  23. data/spec/fixtures/manifest.json +20 -14
  24. data/spec/openapi.json +12099 -5351
  25. metadata +11 -10
  26. data/spec/fixtures/api/teams/api-key-create.json +0 -21
  27. data/spec/fixtures/api/teams/api-key-rotate.json +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a3703f1cf37bec81be8697615f837553379c371e808633793911a603fb9b55e
4
- data.tar.gz: 4bb9e0a6712fa1632e148e72f4f17ed27ef260e54dce60b66416ad7489be6b9f
3
+ metadata.gz: 3705acd09cee16f85cfc7da0a430dd203c928296265a8f412f0b0136dfa27a73
4
+ data.tar.gz: 79d3246ecf68eae368ec605c96cfed1f09be0427f0a9953700b5e3fde5a761a1
5
5
  SHA512:
6
- metadata.gz: d995c7bcd72fe27223c8dfd6ba96ce3556b6f856b1150bed5b76bf74f2f1f8d6359a1bfb757aabce85f6b02decc2f1dd35b5b7f623bc9f2c12cc60e44bebd9d1
7
- data.tar.gz: 602db5a36951402b18e211e651135a1c779cbeec6c7788281f9aa8a7717d418df1ea3e3235b2abfec524d5e861b23113fa308d5b80448d0fd2b8c03d814e26fb
6
+ metadata.gz: 04bcd559dba66a6677ef3bb2d09ff55f55b7c50598e7883d9c124edd3986213564ed8de8ebfdc351db4b1408fddf4d4403583c53949b37e9a7c1dd8c0a4db387
7
+ data.tar.gz: 0cf760c24506a05d64164773e5cc8c0b1c3d17607f3770b240ad4c4d0fa7400a79ccf8f37d545c2c6f3ec8240d5f7ed8e45a84dcb121f26fde60e3ba8c7524b8
data/README.md CHANGED
@@ -4,13 +4,14 @@
4
4
  ![Ruby 3.3+](https://img.shields.io/badge/ruby-3.3%2B-CC342D?logo=ruby&logoColor=white)
5
5
  ![License: MIT](https://img.shields.io/badge/license-MIT-0f766e.svg)
6
6
 
7
- The Tripwire Ruby library provides convenient access to the Tripwire API from applications written in Ruby. It includes a client for Sessions, visitor fingerprints, Teams, Team API key management, sealed token verification, Gate, and Gate delivery/webhook helpers.
7
+ The Tripwire Ruby library provides convenient access to the Tripwire API from applications written in Ruby. It includes a client for Sessions, visitor fingerprints, Organizations, Organization API key management, sealed token verification, Gate, and Gate delivery/webhook helpers.
8
8
 
9
9
  The library also provides:
10
10
 
11
11
  - a fast configuration path using `TRIPWIRE_SECRET_KEY`
12
12
  - lazy helpers for cursor-based pagination
13
13
  - structured API errors and built-in sealed token verification
14
+ - webhook endpoint management, test sends, and event delivery history
14
15
  - public, bearer-token, and secret-key auth modes for Gate flows
15
16
  - Gate delivery/webhook helpers
16
17
 
@@ -72,20 +73,39 @@ fingerprint = client.fingerprints.get("vid_0123456789abcdefghjkmnpqrs")
72
73
  puts fingerprint[:id]
73
74
  ```
74
75
 
75
- ### Teams
76
+ ### Organizations
76
77
 
77
78
  ```ruby
78
- team = client.teams.get("team_0123456789abcdefghjkmnpqrs")
79
- updated = client.teams.update("team_0123456789abcdefghjkmnpqrs", name: "New Name")
79
+ organization = client.organizations.get("org_0123456789abcdefghjkmnpqrs")
80
+ updated = client.organizations.update("org_0123456789abcdefghjkmnpqrs", name: "New Name")
80
81
 
81
82
  puts updated[:name]
82
83
  ```
83
84
 
84
- ### Team API keys
85
+ ### Organization API keys
85
86
 
86
87
  ```ruby
87
- created = client.teams.api_keys.create("team_0123456789abcdefghjkmnpqrs", name: "Production", environment: "live")
88
- client.teams.api_keys.revoke("team_0123456789abcdefghjkmnpqrs", created[:id])
88
+ created = client.organizations.api_keys.create("org_0123456789abcdefghjkmnpqrs", name: "Production", type: "secret", environment: "live")
89
+ client.organizations.api_keys.revoke("org_0123456789abcdefghjkmnpqrs", created[:id])
90
+ ```
91
+
92
+ ### Webhooks
93
+
94
+ ```ruby
95
+ endpoint = client.webhooks.create_endpoint(
96
+ "org_0123456789abcdefghjkmnpqrs",
97
+ name: "Production alerts",
98
+ url: "https://example.com/tripwire/webhook",
99
+ event_types: ["session.result.persisted", "gate.session.approved"]
100
+ )
101
+
102
+ events = client.webhooks.list_events(
103
+ "org_0123456789abcdefghjkmnpqrs",
104
+ endpoint_id: endpoint[:id],
105
+ type: "session.result.persisted"
106
+ )
107
+
108
+ puts events.items.first[:webhook_deliveries].first[:status]
89
109
  ```
90
110
 
91
111
  ### Gate APIs
@@ -10,7 +10,7 @@ module Tripwire
10
10
  DEFAULT_TIMEOUT = 30
11
11
  SDK_CLIENT_HEADER = "tripwire-server-ruby/0.1.0".freeze
12
12
 
13
- attr_reader :sessions, :fingerprints, :teams, :gate, :timeout
13
+ attr_reader :sessions, :fingerprints, :organizations, :gate, :webhooks, :timeout
14
14
 
15
15
  def initialize(secret_key: ENV["TRIPWIRE_SECRET_KEY"], base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT, user_agent: nil, transport: nil)
16
16
  @secret_key = secret_key
@@ -21,8 +21,9 @@ module Tripwire
21
21
 
22
22
  @sessions = SessionsResource.new(self)
23
23
  @fingerprints = FingerprintsResource.new(self)
24
- @teams = TeamsResource.new(self)
24
+ @organizations = OrganizationsResource.new(self)
25
25
  @gate = GateResource.new(self)
26
+ @webhooks = WebhooksResource.new(self)
26
27
  end
27
28
 
28
29
  def request_json(method, path, query: {}, body: nil, expect_content: true, auth: { kind: :secret })
@@ -220,30 +221,39 @@ module Tripwire
220
221
  end
221
222
 
222
223
  class ApiKeysResource < BaseResource
223
- def create(team_id, name: nil, environment: nil, allowed_origins: nil, rate_limit: nil)
224
- payload = @client.request_json("POST", "/v1/teams/#{CGI.escape(team_id)}/api-keys", body: compact({
224
+ def create(organization_id, name:, type: nil, environment: nil, allowed_origins: nil, scopes: nil)
225
+ payload = @client.request_json("POST", "/v1/organizations/#{CGI.escape(organization_id)}/api-keys", body: compact({
225
226
  name: name,
227
+ type: type,
226
228
  environment: environment,
227
229
  allowed_origins: allowed_origins,
228
- rate_limit: rate_limit
230
+ scopes: scopes
229
231
  }))
230
232
  payload[:data]
231
233
  end
232
234
 
233
- def list(team_id, limit: nil, cursor: nil)
234
- payload = @client.request_json("GET", "/v1/teams/#{CGI.escape(team_id)}/api-keys", query: {
235
+ def list(organization_id, limit: nil, cursor: nil)
236
+ payload = @client.request_json("GET", "/v1/organizations/#{CGI.escape(organization_id)}/api-keys", query: {
235
237
  limit: limit,
236
238
  cursor: cursor
237
239
  })
238
240
  list_result(payload)
239
241
  end
240
242
 
241
- def revoke(team_id, key_id)
242
- @client.request_json("DELETE", "/v1/teams/#{CGI.escape(team_id)}/api-keys/#{CGI.escape(key_id)}")[:data]
243
+ def update(organization_id, key_id, name: nil, allowed_origins: nil, scopes: nil)
244
+ @client.request_json("PATCH", "/v1/organizations/#{CGI.escape(organization_id)}/api-keys/#{CGI.escape(key_id)}", body: compact({
245
+ name: name,
246
+ allowed_origins: allowed_origins,
247
+ scopes: scopes
248
+ }))[:data]
243
249
  end
244
250
 
245
- def rotate(team_id, key_id)
246
- payload = @client.request_json("POST", "/v1/teams/#{CGI.escape(team_id)}/api-keys/#{CGI.escape(key_id)}/rotations")
251
+ def revoke(organization_id, key_id)
252
+ @client.request_json("DELETE", "/v1/organizations/#{CGI.escape(organization_id)}/api-keys/#{CGI.escape(key_id)}")[:data]
253
+ end
254
+
255
+ def rotate(organization_id, key_id)
256
+ payload = @client.request_json("POST", "/v1/organizations/#{CGI.escape(organization_id)}/api-keys/#{CGI.escape(key_id)}/rotations")
247
257
  payload[:data]
248
258
  end
249
259
 
@@ -254,7 +264,7 @@ module Tripwire
254
264
  end
255
265
  end
256
266
 
257
- class TeamsResource < BaseResource
267
+ class OrganizationsResource < BaseResource
258
268
  attr_reader :api_keys
259
269
 
260
270
  def initialize(client)
@@ -263,15 +273,15 @@ module Tripwire
263
273
  end
264
274
 
265
275
  def create(name:, slug:)
266
- @client.request_json("POST", "/v1/teams", body: { name: name, slug: slug })[:data]
276
+ @client.request_json("POST", "/v1/organizations", body: { name: name, slug: slug })[:data]
267
277
  end
268
278
 
269
- def get(team_id)
270
- @client.request_json("GET", "/v1/teams/#{CGI.escape(team_id)}")[:data]
279
+ def get(organization_id)
280
+ @client.request_json("GET", "/v1/organizations/#{CGI.escape(organization_id)}")[:data]
271
281
  end
272
282
 
273
- def update(team_id, name: nil, status: nil)
274
- @client.request_json("PATCH", "/v1/teams/#{CGI.escape(team_id)}", body: {
283
+ def update(organization_id, name: nil, status: nil)
284
+ @client.request_json("PATCH", "/v1/organizations/#{CGI.escape(organization_id)}", body: {
275
285
  name: name,
276
286
  status: status
277
287
  }.reject { |_key, value| value.nil? })[:data]
@@ -310,7 +320,7 @@ module Tripwire
310
320
  @client.request_json("GET", "/v1/gate/services/#{CGI.escape(service_id)}")[:data]
311
321
  end
312
322
 
313
- def create(id:, name:, description:, website:, webhook_url:, discoverable: nil, dashboard_login_url: nil, webhook_secret: nil, env_vars: nil, docs_url: nil, sdks: nil, branding: nil, consent: nil)
323
+ def create(id:, name:, description:, website:, webhook_endpoint_id:, discoverable: nil, dashboard_login_url: nil, env_vars: nil, docs_url: nil, sdks: nil, branding: nil, consent: nil)
314
324
  @client.request_json("POST", "/v1/gate/services", body: compact({
315
325
  id: id,
316
326
  discoverable: discoverable,
@@ -318,8 +328,7 @@ module Tripwire
318
328
  description: description,
319
329
  website: website,
320
330
  dashboard_login_url: dashboard_login_url,
321
- webhook_url: webhook_url,
322
- webhook_secret: webhook_secret,
331
+ webhook_endpoint_id: webhook_endpoint_id,
323
332
  env_vars: env_vars,
324
333
  docs_url: docs_url,
325
334
  sdks: sdks,
@@ -328,15 +337,14 @@ module Tripwire
328
337
  }))[:data]
329
338
  end
330
339
 
331
- def update(service_id, discoverable: nil, name: nil, description: nil, website: nil, dashboard_login_url: nil, webhook_url: nil, webhook_secret: nil, env_vars: nil, docs_url: nil, sdks: nil, branding: nil, consent: nil)
340
+ def update(service_id, discoverable: nil, name: nil, description: nil, website: nil, dashboard_login_url: nil, webhook_endpoint_id: nil, env_vars: nil, docs_url: nil, sdks: nil, branding: nil, consent: nil)
332
341
  @client.request_json("PATCH", "/v1/gate/services/#{CGI.escape(service_id)}", body: compact({
333
342
  discoverable: discoverable,
334
343
  name: name,
335
344
  description: description,
336
345
  website: website,
337
346
  dashboard_login_url: dashboard_login_url,
338
- webhook_url: webhook_url,
339
- webhook_secret: webhook_secret,
347
+ webhook_endpoint_id: webhook_endpoint_id,
340
348
  env_vars: env_vars,
341
349
  docs_url: docs_url,
342
350
  sdks: sdks,
@@ -356,6 +364,50 @@ module Tripwire
356
364
  end
357
365
  end
358
366
 
367
+ class WebhooksResource < BaseResource
368
+ def list_endpoints(organization_id)
369
+ payload = @client.request_json("GET", "/v1/organizations/#{CGI.escape(organization_id)}/webhooks/endpoints")
370
+ list_result(payload)
371
+ end
372
+
373
+ def create_endpoint(organization_id, name:, url:, event_types:)
374
+ @client.request_json("POST", "/v1/organizations/#{CGI.escape(organization_id)}/webhooks/endpoints", body: {
375
+ name: name,
376
+ url: url,
377
+ event_types: event_types
378
+ })[:data]
379
+ end
380
+
381
+ def update_endpoint(organization_id, endpoint_id, **updates)
382
+ @client.request_json("PATCH", "/v1/organizations/#{CGI.escape(organization_id)}/webhooks/endpoints/#{CGI.escape(endpoint_id)}", body: updates)[:data]
383
+ end
384
+
385
+ def disable_endpoint(organization_id, endpoint_id)
386
+ @client.request_json("DELETE", "/v1/organizations/#{CGI.escape(organization_id)}/webhooks/endpoints/#{CGI.escape(endpoint_id)}")[:data]
387
+ end
388
+
389
+ def rotate_secret(organization_id, endpoint_id)
390
+ @client.request_json("POST", "/v1/organizations/#{CGI.escape(organization_id)}/webhooks/endpoints/#{CGI.escape(endpoint_id)}/rotations")[:data]
391
+ end
392
+
393
+ def send_test(organization_id, endpoint_id)
394
+ @client.request_json("POST", "/v1/organizations/#{CGI.escape(organization_id)}/webhooks/endpoints/#{CGI.escape(endpoint_id)}/test")[:data]
395
+ end
396
+
397
+ def list_events(organization_id, endpoint_id: nil, type: nil, limit: nil)
398
+ payload = @client.request_json("GET", "/v1/organizations/#{CGI.escape(organization_id)}/events", query: {
399
+ endpoint_id: endpoint_id,
400
+ type: type,
401
+ limit: limit
402
+ })
403
+ list_result(payload)
404
+ end
405
+
406
+ def retrieve_event(organization_id, event_id)
407
+ @client.request_json("GET", "/v1/organizations/#{CGI.escape(organization_id)}/events/#{CGI.escape(event_id)}")[:data]
408
+ end
409
+ end
410
+
359
411
  class GateSessionsResource < BaseResource
360
412
  def create(service_id:, account_name:, delivery:, metadata: nil)
361
413
  body = {
@@ -39,6 +39,12 @@ module Tripwire
39
39
  XDG_CONFIG_HOME
40
40
  ].freeze
41
41
  BLOCKED_GATE_ENV_VAR_PREFIXES = %w[NPM_CONFIG_ BUN_CONFIG_ GIT_CONFIG_].freeze
42
+ WEBHOOK_EVENT_TYPES = %w[
43
+ session.fingerprint.calculated
44
+ session.result.persisted
45
+ gate.session.approved
46
+ webhook.test
47
+ ].freeze
42
48
  GATE_DELIVERY_HKDF_INFO = "tripwire-gate-delivery:v1".b.freeze
43
49
  X25519_SPKI_PREFIX = ["302a300506032b656e032100"].pack("H*").freeze
44
50
 
@@ -217,7 +223,7 @@ module Tripwire
217
223
 
218
224
  def validate_gate_approved_webhook_payload(value)
219
225
  candidate = symbolize(value)
220
- raise ArgumentError, "event must be gate.session.approved" unless candidate[:event] == "gate.session.approved"
226
+ raise ArgumentError, "webhook payload must not include event; use the webhook event envelope type" if candidate.key?(:event)
221
227
  raise ArgumentError, "service_id is required" if candidate[:service_id].to_s.empty?
222
228
  raise ArgumentError, "gate_session_id is required" if candidate[:gate_session_id].to_s.empty?
223
229
  raise ArgumentError, "gate_account_id is required" if candidate[:gate_account_id].to_s.empty?
@@ -230,7 +236,6 @@ module Tripwire
230
236
  raise ArgumentError, "tripwire.score must be a number or null" unless score.nil? || score.is_a?(Numeric)
231
237
 
232
238
  {
233
- event: "gate.session.approved",
234
239
  service_id: candidate[:service_id],
235
240
  gate_session_id: candidate[:gate_session_id],
236
241
  gate_account_id: candidate[:gate_account_id],
@@ -255,6 +260,26 @@ module Tripwire
255
260
  false
256
261
  end
257
262
 
263
+ def parse_webhook_event(raw_body)
264
+ envelope = symbolize(JSON.parse(raw_body))
265
+ raise ArgumentError, "webhook event object must be webhook_event" unless envelope[:object] == "webhook_event"
266
+ raise ArgumentError, "webhook event id is required" if envelope[:id].to_s.empty?
267
+ raise ArgumentError, "webhook event type is required" if envelope[:type].to_s.empty?
268
+ raise ArgumentError, "unsupported webhook event type: #{envelope[:type]}" unless WEBHOOK_EVENT_TYPES.include?(envelope[:type])
269
+ raise ArgumentError, "webhook event created timestamp is required" if envelope[:created].to_s.empty?
270
+ raise ArgumentError, "webhook event data must be an object" unless envelope[:data].is_a?(Hash)
271
+
272
+ envelope[:data] = validate_gate_approved_webhook_payload(envelope[:data]) if envelope[:type] == "gate.session.approved"
273
+ envelope
274
+ end
275
+
276
+ def verify_and_parse_webhook_event(secret:, timestamp:, raw_body:, signature:, max_age_seconds: 300, now_seconds: nil)
277
+ unless verify_gate_webhook_signature(secret: secret, timestamp: timestamp, raw_body: raw_body, signature: signature, max_age_seconds: max_age_seconds, now_seconds: now_seconds)
278
+ raise ArgumentError, "Invalid Tripwire webhook signature"
279
+ end
280
+ parse_webhook_event(raw_body)
281
+ end
282
+
258
283
  def symbolize(value)
259
284
  case value
260
285
  when Array
@@ -1,5 +1,5 @@
1
1
  module Tripwire
2
2
  module Server
3
- VERSION = "0.3.1".freeze
3
+ VERSION = "0.3.2".freeze
4
4
  end
5
5
  end
data/spec/README.md CHANGED
@@ -15,8 +15,8 @@ Server SDKs include only customer-facing APIs:
15
15
 
16
16
  - `/v1/sessions`
17
17
  - `/v1/fingerprints`
18
- - `/v1/teams`
19
- - `/v1/teams/:teamId/api-keys`
18
+ - `/v1/organizations`
19
+ - `/v1/organizations/:organizationId/api-keys`
20
20
  - `/v1/gate/registry`
21
21
  - `/v1/gate/services`
22
22
  - `/v1/gate/sessions`
@@ -44,11 +44,11 @@ Every server SDK should expose these top-level capabilities:
44
44
  - list
45
45
  - get
46
46
  - iterator / auto-pagination helper
47
- - Teams
47
+ - Organizations
48
48
  - create
49
49
  - get
50
50
  - update
51
- - Team API keys
51
+ - Organization API keys
52
52
  - create
53
53
  - list
54
54
  - revoke
@@ -8,7 +8,6 @@
8
8
  "description": "Acme production signup flow",
9
9
  "website": "https://acme.example.com",
10
10
  "dashboard_login_url": "https://dashboard.acme.example.com/auth/gate",
11
- "webhook_url": "https://api.acme.example.com/v1/gate/webhook",
12
11
  "env_vars": [
13
12
  {
14
13
  "name": "Publishable key",
@@ -41,7 +40,8 @@
41
40
  "privacy_url": "https://acme.example.com/privacy"
42
41
  },
43
42
  "created_at": "2026-04-03T20:00:00.000Z",
44
- "updated_at": "2026-04-03T20:00:00.000Z"
43
+ "updated_at": "2026-04-03T20:00:00.000Z",
44
+ "webhook_endpoint_id": "we_0123456789abcdef0123456789abcdef"
45
45
  },
46
46
  "meta": {
47
47
  "request_id": "req_0123456789abcdef0123456789abcdef"
@@ -8,7 +8,6 @@
8
8
  "description": "Acme production signup flow",
9
9
  "website": "https://acme.example.com",
10
10
  "dashboard_login_url": "https://dashboard.acme.example.com/auth/gate",
11
- "webhook_url": "https://api.acme.example.com/v1/gate/webhook",
12
11
  "env_vars": [
13
12
  {
14
13
  "name": "Publishable key",
@@ -41,7 +40,8 @@
41
40
  "privacy_url": "https://acme.example.com/privacy"
42
41
  },
43
42
  "created_at": "2026-04-03T20:00:00.000Z",
44
- "updated_at": "2026-04-03T20:05:00.000Z"
43
+ "updated_at": "2026-04-03T20:05:00.000Z",
44
+ "webhook_endpoint_id": "we_0123456789abcdef0123456789abcdef"
45
45
  },
46
46
  "meta": {
47
47
  "request_id": "req_0123456789abcdef0123456789abcdef"
@@ -8,7 +8,6 @@
8
8
  "description": "Acme production signup flow",
9
9
  "website": "https://acme.example.com",
10
10
  "dashboard_login_url": "https://dashboard.acme.example.com/auth/gate",
11
- "webhook_url": "https://api.acme.example.com/v1/gate/webhook",
12
11
  "env_vars": [
13
12
  {
14
13
  "name": "Publishable key",
@@ -41,7 +40,8 @@
41
40
  "privacy_url": "https://acme.example.com/privacy"
42
41
  },
43
42
  "created_at": "2026-04-03T20:00:00.000Z",
44
- "updated_at": "2026-04-03T20:15:00.000Z"
43
+ "updated_at": "2026-04-03T20:15:00.000Z",
44
+ "webhook_endpoint_id": "we_0123456789abcdef0123456789abcdef"
45
45
  },
46
46
  "meta": {
47
47
  "request_id": "req_0123456789abcdef0123456789abcdef"
@@ -8,7 +8,6 @@
8
8
  "description": "Acme production signup flow",
9
9
  "website": "https://acme.example.com",
10
10
  "dashboard_login_url": "https://dashboard.acme.example.com/auth/gate",
11
- "webhook_url": "https://api.acme.example.com/v1/gate/webhook",
12
11
  "env_vars": [
13
12
  {
14
13
  "name": "Publishable key",
@@ -41,7 +40,8 @@
41
40
  "privacy_url": "https://acme.example.com/privacy"
42
41
  },
43
42
  "created_at": "2026-04-03T20:00:00.000Z",
44
- "updated_at": "2026-04-03T20:10:00.000Z"
43
+ "updated_at": "2026-04-03T20:10:00.000Z",
44
+ "webhook_endpoint_id": "we_0123456789abcdef0123456789abcdef"
45
45
  },
46
46
  "meta": {
47
47
  "request_id": "req_0123456789abcdef0123456789abcdef"
@@ -9,7 +9,6 @@
9
9
  "description": "Acme production signup flow",
10
10
  "website": "https://acme.example.com",
11
11
  "dashboard_login_url": "https://dashboard.acme.example.com/auth/gate",
12
- "webhook_url": "https://api.acme.example.com/v1/gate/webhook",
13
12
  "env_vars": [
14
13
  {
15
14
  "name": "Publishable key",
@@ -42,7 +41,8 @@
42
41
  "privacy_url": "https://acme.example.com/privacy"
43
42
  },
44
43
  "created_at": "2026-04-03T20:00:00.000Z",
45
- "updated_at": "2026-04-03T20:05:00.000Z"
44
+ "updated_at": "2026-04-03T20:05:00.000Z",
45
+ "webhook_endpoint_id": "we_0123456789abcdef0123456789abcdef"
46
46
  }
47
47
  ],
48
48
  "meta": {
@@ -0,0 +1,27 @@
1
+ {
2
+ "data": {
3
+ "object": "api_key",
4
+ "id": "key_2y4grs8zpxb7n1q3dm5kc9wfta",
5
+ "type": "secret",
6
+ "name": "Production Backend",
7
+ "environment": "live",
8
+ "status": "active",
9
+ "allowed_origins": null,
10
+ "scopes": [
11
+ "sessions:list",
12
+ "sessions:read",
13
+ "api_keys:read"
14
+ ],
15
+ "rate_limit": null,
16
+ "key_preview": "sk_live_[example]...",
17
+ "revealed_key": "sk_live_[example_secret_key]",
18
+ "last_used_at": null,
19
+ "created_at": "2026-03-24T19:00:00.000Z",
20
+ "rotated_at": null,
21
+ "revoked_at": null,
22
+ "grace_expires_at": null
23
+ },
24
+ "meta": {
25
+ "request_id": "req_0123456789abcdef0123456789abcdef"
26
+ }
27
+ }
@@ -3,17 +3,22 @@
3
3
  {
4
4
  "object": "api_key",
5
5
  "id": "key_6789abcdefghjkmnpqrstvwxyz",
6
- "public_key": "pk_live_example",
7
- "name": "Production",
6
+ "type": "publishable",
7
+ "name": "Acme Web App",
8
8
  "environment": "live",
9
+ "status": "active",
9
10
  "allowed_origins": [
10
11
  "https://example.com"
11
12
  ],
12
- "rate_limit": 600,
13
- "status": "active",
13
+ "scopes": null,
14
+ "rate_limit": null,
15
+ "key_preview": "pk_live_[example]...",
16
+ "display_key": "pk_live_[example_publishable_key]",
17
+ "last_used_at": "2026-03-24T19:30:00.000Z",
14
18
  "created_at": "2026-03-24T19:00:00.000Z",
15
19
  "rotated_at": null,
16
- "revoked_at": null
20
+ "revoked_at": null,
21
+ "grace_expires_at": null
17
22
  }
18
23
  ],
19
24
  "pagination": {
@@ -2,17 +2,22 @@
2
2
  "data": {
3
3
  "object": "api_key",
4
4
  "id": "key_6789abcdefghjkmnpqrstvwxyz",
5
- "public_key": "pk_live_example",
6
- "name": "Production",
5
+ "type": "publishable",
6
+ "name": "Acme Web App",
7
7
  "environment": "live",
8
+ "status": "revoked",
8
9
  "allowed_origins": [
9
10
  "https://example.com"
10
11
  ],
11
- "rate_limit": 600,
12
- "status": "revoked",
12
+ "scopes": null,
13
+ "rate_limit": null,
14
+ "key_preview": "pk_live_[example]...",
15
+ "display_key": "pk_live_[example_publishable_key]",
16
+ "last_used_at": "2026-03-24T19:30:00.000Z",
13
17
  "created_at": "2026-03-24T19:00:00.000Z",
14
18
  "rotated_at": null,
15
- "revoked_at": "2026-03-24T20:05:00.000Z"
19
+ "revoked_at": "2026-03-24T20:05:00.000Z",
20
+ "grace_expires_at": null
16
21
  },
17
22
  "meta": {
18
23
  "request_id": "req_0123456789abcdef0123456789abcdef"
@@ -0,0 +1,27 @@
1
+ {
2
+ "data": {
3
+ "object": "api_key",
4
+ "id": "key_789abcdefghjkmnpqrstvwxyz0",
5
+ "type": "secret",
6
+ "name": "Production Backend",
7
+ "environment": "live",
8
+ "status": "active",
9
+ "allowed_origins": null,
10
+ "scopes": [
11
+ "sessions:list",
12
+ "sessions:read",
13
+ "api_keys:manage"
14
+ ],
15
+ "rate_limit": null,
16
+ "key_preview": "sk_live_[rotated_example]...",
17
+ "revealed_key": "sk_live_[rotated_example_secret_key]",
18
+ "last_used_at": null,
19
+ "created_at": "2026-03-24T19:00:00.000Z",
20
+ "rotated_at": null,
21
+ "revoked_at": null,
22
+ "grace_expires_at": null
23
+ },
24
+ "meta": {
25
+ "request_id": "req_0123456789abcdef0123456789abcdef"
26
+ }
27
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "data": {
3
+ "object": "api_key",
4
+ "id": "key_6789abcdefghjkmnpqrstvwxyz",
5
+ "type": "publishable",
6
+ "name": "Updated Web App",
7
+ "environment": "live",
8
+ "status": "active",
9
+ "allowed_origins": [
10
+ "https://example.com",
11
+ "https://app.example.com"
12
+ ],
13
+ "scopes": [
14
+ "sessions:list",
15
+ "sessions:read"
16
+ ],
17
+ "rate_limit": null,
18
+ "key_preview": "pk_live_[example]...",
19
+ "display_key": "pk_live_[example_publishable_key]",
20
+ "last_used_at": "2026-03-24T19:30:00.000Z",
21
+ "created_at": "2026-03-24T19:00:00.000Z",
22
+ "rotated_at": null,
23
+ "revoked_at": null,
24
+ "grace_expires_at": null
25
+ },
26
+ "meta": {
27
+ "request_id": "req_0123456789abcdef0123456789abcdef"
28
+ }
29
+ }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "data": {
3
- "object": "team",
4
- "id": "team_56789abcdefghjkmnpqrstvwxy",
5
- "name": "Example Team",
6
- "slug": "example-team",
3
+ "object": "organization",
4
+ "id": "org_56789abcdefghjkmnpqrstvwxy",
5
+ "name": "Example Organization",
6
+ "slug": "example-organization",
7
7
  "status": "active",
8
8
  "created_at": "2026-03-24T19:00:00.000Z",
9
9
  "updated_at": "2026-03-24T19:10:00.000Z"
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "data": {
3
- "object": "team",
4
- "id": "team_56789abcdefghjkmnpqrstvwxy",
5
- "name": "Example Team",
6
- "slug": "example-team",
3
+ "object": "organization",
4
+ "id": "org_56789abcdefghjkmnpqrstvwxy",
5
+ "name": "Example Organization",
6
+ "slug": "example-organization",
7
7
  "status": "suspended",
8
8
  "created_at": "2026-03-24T19:00:00.000Z",
9
9
  "updated_at": "2026-03-24T19:12:00.000Z"
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "data": {
3
- "object": "team",
4
- "id": "team_56789abcdefghjkmnpqrstvwxy",
5
- "name": "Example Team",
6
- "slug": "example-team",
3
+ "object": "organization",
4
+ "id": "org_56789abcdefghjkmnpqrstvwxy",
5
+ "name": "Example Organization",
6
+ "slug": "example-organization",
7
7
  "status": "active",
8
8
  "created_at": "2026-03-24T19:00:00.000Z",
9
9
  "updated_at": "2026-03-24T19:10:00.000Z"