immersivecommons 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 64529458b7a3d2d3702de67e334dfa5bc67ad9a1d18dff908c529414caa32ebf
4
+ data.tar.gz: 699bba540214da82cd7aedc674e90afae0f2798b5c6ed68d5ee7d0356f8e1f85
5
+ SHA512:
6
+ metadata.gz: 15feca82dd9b11b815209fe3e784ea7563538fda457c3a9eac5a20dec79d35c141a31735c50d2b0d4588565cabf67b7158c61379ede3dd109fd386a007627e8c
7
+ data.tar.gz: 22b1954531b9781c575da9f38196189443e13ff912cfeb2a5b2bd32406a18bf0af441f52361a6059f63093076ee67da4503ef4c16e487939031e5eb56d0892df
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Immersive Commons
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # immersivecommons (Ruby) — Immersive Commons client
2
+
3
+ Thin, spec-derived Ruby client for the [Immersive Commons](https://www.immersivecommons.com) Agent REST API. Immersive Commons is Floor 10 of Frontier Tower, a members-run AI builder space in San Francisco — this gem gives Ruby agents and services typed access to events & RSVPs, the members directory, resource booking, research Q&A, the donor wall, and RFC 8628 device-code token minting.
4
+
5
+ - **Homepage:** <https://www.immersivecommons.com>
6
+ - **Developer docs:** <https://www.immersivecommons.com/developers>
7
+ - **OpenAPI spec:** <https://www.immersivecommons.com/openapi.json> (vendored here as `lib/immersivecommons/openapi.json`)
8
+ - **Siblings:** [`@immersivecommons/sdk`](https://www.npmjs.com/package/@immersivecommons/sdk) (npm), [`immersivecommons`](https://pypi.org/project/immersivecommons/) (PyPI), [`ic-go`](https://pkg.go.dev/github.com/immersive-commons/ic-go) (Go)
9
+
10
+ ## Install
11
+
12
+ ```sh
13
+ gem install immersivecommons
14
+ ```
15
+
16
+ Zero runtime dependencies — stdlib `net/http` only. Ruby >= 3.0.
17
+
18
+ ## Use
19
+
20
+ ```ruby
21
+ require "immersivecommons"
22
+
23
+ # Public reads need no token.
24
+ ic = ImmersiveCommons::Client.new
25
+ events = ic.list_upcoming_events(limit: 5)
26
+ puts events["count"]
27
+
28
+ # Authenticated calls take an agent bearer token (agt_...).
29
+ authed = ImmersiveCommons::Client.new(token: "agt_your_token")
30
+ puts authed.setup_check
31
+
32
+ # Writes that the spec marks idempotent accept an Idempotency-Key.
33
+ receipt = authed.rsvp_to_event("https://luma.com/your-event",
34
+ email: "you@example.com",
35
+ name: "Your Name",
36
+ idempotency_key: "rsvp-key-1")
37
+ ```
38
+
39
+ ### Minting a token (device-code grant)
40
+
41
+ ```ruby
42
+ res = ic.authorize(["events:rsvp"], client_name: "my-agent",
43
+ on_prompt: ->(start) {
44
+ puts "Visit #{start['verify_url']} and enter code #{start['user_code']}"
45
+ })
46
+ ic.token = res["token"]
47
+ ```
48
+
49
+ A human approves the grant in the browser; the loop polls until `completed`. Pass `sandbox: true` to `Client.new` (or `authorize`) to mint a sandbox token whose writes return simulated receipts.
50
+
51
+ ## How it derives from the spec
52
+
53
+ `lib/immersivecommons/generated.rb` is emitted from the vendored `openapi.json` by `scripts/generate.py`: an operations table (operationId → method, path, query params, scopes, auth, idempotency, sandbox behavior). The transport is table-driven — one generic `call(operation_id, ...)` builds the URL, attaches the bearer token, and adds an `Idempotency-Key` only on operations the spec marks idempotent. The named methods are facades over that one call, so they cannot drift from the spec. Errors raise `ImmersiveCommons::ApiError` with `status`, `error_kind`, and `retry_after_seconds`.
54
+
55
+ 23 operations are in the table; the 4 Clerk-cookie-only operations (`tailAgentEvents`, `getMyTier`, `requestTier`, `cancelTierRequest`) have no facade methods — agents use the equivalent MCP tools instead. `batchPublicReads` is reachable via the generic `call`.
56
+
57
+ ## Test
58
+
59
+ ```sh
60
+ ruby -Ilib test/test_client.rb
61
+ ```
62
+
63
+ Mock transport, no network, no live writes.
64
+
65
+ ## Source of truth
66
+
67
+ This gem is generated from and mirrored out of the [immersivecommons.com](https://www.immersivecommons.com) site repo; skills and sibling SDKs live in [immersive-commons/ic-skills](https://github.com/immersive-commons/ic-skills). MIT license.
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Thin, table-driven client for the Immersive Commons Agent REST API.
4
+ #
5
+ # `call` is the whole transport: it looks the operation up in the generated
6
+ # OPERATIONS table, attaches auth + Idempotency-Key when the spec allows, and
7
+ # sends the request. The named methods are typed facades over `call` — they
8
+ # add no routing of their own, so they cannot drift from the spec. Zero
9
+ # dependencies (stdlib net/http only).
10
+
11
+ require "json"
12
+ require "net/http"
13
+ require "timeout"
14
+ require "uri"
15
+
16
+ module ImmersiveCommons
17
+ # Raised on a non-2xx response. Carries the parsed error body + metadata.
18
+ class ApiError < StandardError
19
+ attr_reader :status, :operation_id, :body, :error_kind, :retry_after_seconds
20
+
21
+ def initialize(status, operation_id, body)
22
+ @status = status
23
+ @operation_id = operation_id
24
+ @body = body.is_a?(Hash) ? body : {}
25
+ @error_kind = @body["error_kind"].is_a?(String) ? @body["error_kind"] : nil
26
+ ra = @body["retry_after_seconds"]
27
+ @retry_after_seconds = ra.is_a?(Integer) ? ra : nil
28
+
29
+ err = @body["error"]
30
+ msg = if err.is_a?(String)
31
+ err
32
+ elsif err.is_a?(Hash)
33
+ [err["code"], err["message"]].compact.join(": ")
34
+ end
35
+ msg = @body["message"] if (msg.nil? || msg.empty?) && @body["message"].is_a?(String)
36
+ text = "#{operation_id} failed (HTTP #{status})"
37
+ text += ": #{msg}" if msg && !msg.empty?
38
+ super(text)
39
+ end
40
+ end
41
+
42
+ class Client
43
+ attr_reader :base_url, :sandbox, :user_agent
44
+ attr_accessor :token
45
+
46
+ # transport: an object responding to call(method, url, headers, body) ->
47
+ # [status, headers, body_text]. Injectable for tests; defaults to Net::HTTP.
48
+ def initialize(token: nil, base_url: DEFAULT_BASE_URL, sandbox: false,
49
+ timeout: 30, transport: nil, user_agent: nil)
50
+ @base_url = base_url.sub(%r{/+\z}, "")
51
+ @token = token
52
+ # Documentation flag only: a sandbox token already behaves sandbox
53
+ # server-side. This just makes authorize request one by default.
54
+ @sandbox = sandbox
55
+ @user_agent = user_agent || "immersivecommons-ruby/#{API_VERSION}"
56
+ @transport = transport || default_transport(timeout)
57
+ end
58
+
59
+ # ---------------------------------------------------------------- generic
60
+
61
+ def call(operation_id, query: nil, body: nil, idempotency_key: nil, token: nil)
62
+ spec = OPERATIONS[operation_id]
63
+ raise ArgumentError, "Unknown operationId: #{operation_id}" unless spec
64
+
65
+ headers = { "accept" => "application/json", "user-agent" => @user_agent }
66
+ tok = token || @token
67
+ headers["authorization"] = "Bearer #{tok}" if tok
68
+
69
+ data = nil
70
+ if spec[:has_body] && !body.nil?
71
+ headers["content-type"] = "application/json"
72
+ data = JSON.generate(body)
73
+ end
74
+
75
+ if idempotency_key
76
+ unless spec[:idempotent]
77
+ raise ArgumentError, "#{operation_id} does not accept an Idempotency-Key"
78
+ end
79
+ headers["idempotency-key"] = idempotency_key
80
+ end
81
+
82
+ url = @base_url + spec[:path] + query_string(query)
83
+ status, _resp_headers, text = @transport.call(spec[:method], url, headers, data)
84
+ parsed = begin
85
+ text && !text.empty? ? JSON.parse(text) : nil
86
+ rescue JSON::ParserError
87
+ nil
88
+ end
89
+ unless (200..299).cover?(status)
90
+ raise ApiError.new(status, operation_id, parsed || { "error" => text })
91
+ end
92
+ parsed
93
+ end
94
+
95
+ # ------------------------------------------------------------------ reads
96
+
97
+ def list_upcoming_events(limit: nil)
98
+ call("listUpcomingEvents", query: limit.nil? ? nil : { "limit" => limit })
99
+ end
100
+
101
+ def get_event_by_luma_url(luma)
102
+ call("getEventByLumaUrl", query: { "luma" => luma })
103
+ end
104
+
105
+ def search_directory(q: nil, limit: nil)
106
+ call("searchDirectory", query: { "q" => q, "limit" => limit })
107
+ end
108
+
109
+ def list_resources
110
+ call("listResources")
111
+ end
112
+
113
+ def get_my_activity(limit: nil)
114
+ call("getMyActivity", query: limit.nil? ? nil : { "limit" => limit })
115
+ end
116
+
117
+ def get_my_leaderboard_status
118
+ call("getMyLeaderboardStatus")
119
+ end
120
+
121
+ def get_donor_wall(limit: nil)
122
+ call("getDonorWall", query: limit.nil? ? nil : { "limit" => limit })
123
+ end
124
+
125
+ def setup_check
126
+ call("setupCheck")
127
+ end
128
+
129
+ # ----------------------------------------------------------------- writes
130
+
131
+ def rsvp_to_event(event_url, email:, name: nil, idempotency_key: nil)
132
+ body = { "event_url" => event_url, "email" => email }
133
+ body["name"] = name unless name.nil?
134
+ call("rsvpToEvent", body: body, idempotency_key: idempotency_key)
135
+ end
136
+
137
+ def request_event(event, idempotency_key: nil)
138
+ call("requestEvent", body: event, idempotency_key: idempotency_key)
139
+ end
140
+
141
+ def book_resource(resource_id, start_iso:, end_iso:, email:, purpose: nil, idempotency_key: nil)
142
+ body = { "resource_id" => resource_id, "start_iso" => start_iso,
143
+ "end_iso" => end_iso, "email" => email }
144
+ body["purpose"] = purpose unless purpose.nil?
145
+ call("bookResource", body: body, idempotency_key: idempotency_key)
146
+ end
147
+
148
+ def set_leaderboard_optin(opt_in)
149
+ call("setLeaderboardOptIn", body: { "optIn" => opt_in })
150
+ end
151
+
152
+ def ask_research(q, k: nil, sources: nil, synthesize: nil, model: nil)
153
+ body = { "q" => q }
154
+ body["k"] = k unless k.nil?
155
+ body["sources"] = sources.to_a unless sources.nil?
156
+ body["synthesize"] = synthesize unless synthesize.nil?
157
+ body["model"] = model unless model.nil?
158
+ call("askResearch", body: body)
159
+ end
160
+
161
+ def submit_highlight_pending(story, idempotency_key: nil)
162
+ call("submitHighlightPending", body: story, idempotency_key: idempotency_key)
163
+ end
164
+
165
+ def submit_feedback(kind, message, **extra)
166
+ body = { "kind" => kind, "message" => message }
167
+ extra.each { |k, v| body[k.to_s] = v }
168
+ call("submitFeedback", body: body)
169
+ end
170
+
171
+ def revoke_own_token
172
+ call("revokeOwnToken")
173
+ end
174
+
175
+ # --------------------------------------------------------- device-code auth
176
+
177
+ def start_signup(scopes, client_name: nil, sandbox: nil)
178
+ body = { "scopes" => scopes.to_a }
179
+ body["client_name"] = client_name unless client_name.nil?
180
+ want = sandbox.nil? ? @sandbox : sandbox
181
+ body["sandbox"] = true if want
182
+ call("startSignup", body: body)
183
+ end
184
+
185
+ def poll_signup(device_code)
186
+ call("pollSignup", query: { "device_code" => device_code })
187
+ end
188
+
189
+ # Runs the full RFC 8628 device-code grant and returns the minted token as
190
+ # { "token", "granted_scopes", "tier", "sandbox" }. Does not store the
191
+ # token on the client — assign client.token yourself if you want to.
192
+ # on_prompt receives the start response (user_code, verify_url).
193
+ # sleeper is injectable for tests.
194
+ def authorize(scopes, client_name: "immersivecommons-ruby", sandbox: nil,
195
+ on_prompt: nil, timeout_seconds: nil, sleeper: ->(s) { sleep(s) })
196
+ start = start_signup(scopes, client_name: client_name, sandbox: sandbox)
197
+ on_prompt&.call(start)
198
+ interval = [start["interval"].to_i, 1].max
199
+ deadline = monotonic_now + (timeout_seconds || start["expires_in"] || 900)
200
+ loop do
201
+ raise Timeout::Error, "Device-code grant timed out before approval." if monotonic_now > deadline
202
+
203
+ sleeper.call(interval)
204
+ poll = poll_signup(start.fetch("device_code"))
205
+ case poll["status"]
206
+ when "completed"
207
+ unless poll["agent_token"]
208
+ raise ApiError.new(500, "pollSignup", { "error" => "completed without token" })
209
+ end
210
+ return {
211
+ "token" => poll["agent_token"],
212
+ "granted_scopes" => poll["granted_scopes"] || [],
213
+ "tier" => poll["tier"],
214
+ "sandbox" => !!poll["sandbox"]
215
+ }
216
+ when "cancelled"
217
+ raise "Device-code grant cancelled: #{poll['reason']}".strip
218
+ end
219
+ # pending -> keep polling
220
+ end
221
+ end
222
+
223
+ private
224
+
225
+ def monotonic_now
226
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
227
+ end
228
+
229
+ def query_string(query)
230
+ return "" if query.nil?
231
+
232
+ pairs = query.reject { |_k, v| v.nil? }
233
+ return "" if pairs.empty?
234
+
235
+ "?" + URI.encode_www_form(pairs)
236
+ end
237
+
238
+ def default_transport(timeout)
239
+ lambda do |method, url, headers, body|
240
+ uri = URI.parse(url)
241
+ http = Net::HTTP.new(uri.host, uri.port)
242
+ http.use_ssl = uri.scheme == "https"
243
+ http.open_timeout = timeout
244
+ http.read_timeout = timeout
245
+ req = Net::HTTP.const_get(method.capitalize).new(uri, headers)
246
+ req.body = body if body
247
+ resp = http.request(req)
248
+ [resp.code.to_i, resp.each_header.to_h, resp.body || ""]
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,291 @@
1
+ # frozen_string_literal: true
2
+
3
+ # AUTO-GENERATED from openapi.json by scripts/generate.py — DO NOT EDIT.
4
+ # Spec version: 2026-07-16. Regenerate with: python scripts/generate.py
5
+
6
+ module ImmersiveCommons
7
+ API_VERSION = "2026-07-16"
8
+ DEFAULT_BASE_URL = "https://www.immersivecommons.com"
9
+
10
+ # operationId => operation spec (method, path, query, scopes, auth,
11
+ # idempotency, sandbox). The client transport is table-driven off this
12
+ # hash, so no method can describe a route the spec does not.
13
+ OPERATIONS = {
14
+ "listUpcomingEvents" => {
15
+ method: "GET",
16
+ path: "/api/events/upcoming",
17
+ query: [{ name: "limit", required: false }],
18
+ scopes: ["events:read_upcoming"],
19
+ requires_auth: false,
20
+ bearer_reachable: true,
21
+ browser_session: false,
22
+ idempotent: false,
23
+ has_body: false,
24
+ sandbox: nil
25
+ }.freeze,
26
+ "getEventByLumaUrl" => {
27
+ method: "GET",
28
+ path: "/api/events/get",
29
+ query: [{ name: "luma", required: true }],
30
+ scopes: [],
31
+ requires_auth: false,
32
+ bearer_reachable: true,
33
+ browser_session: false,
34
+ idempotent: false,
35
+ has_body: false,
36
+ sandbox: nil
37
+ }.freeze,
38
+ "batchPublicReads" => {
39
+ method: "POST",
40
+ path: "/api/batch",
41
+ query: [],
42
+ scopes: [],
43
+ requires_auth: false,
44
+ bearer_reachable: true,
45
+ browser_session: false,
46
+ idempotent: false,
47
+ has_body: true,
48
+ sandbox: "real"
49
+ }.freeze,
50
+ "tailAgentEvents" => {
51
+ method: "GET",
52
+ path: "/api/events/next",
53
+ query: [{ name: "since", required: false }, { name: "types", required: false }, { name: "limit", required: false }],
54
+ scopes: [],
55
+ requires_auth: true,
56
+ bearer_reachable: false,
57
+ browser_session: true,
58
+ idempotent: false,
59
+ has_body: false,
60
+ sandbox: nil
61
+ }.freeze,
62
+ "rsvpToEvent" => {
63
+ method: "POST",
64
+ path: "/api/events/rsvp",
65
+ query: [],
66
+ scopes: ["events:rsvp"],
67
+ requires_auth: true,
68
+ bearer_reachable: true,
69
+ browser_session: false,
70
+ idempotent: true,
71
+ has_body: true,
72
+ sandbox: "simulated"
73
+ }.freeze,
74
+ "requestEvent" => {
75
+ method: "POST",
76
+ path: "/api/events/request",
77
+ query: [],
78
+ scopes: ["events:request"],
79
+ requires_auth: true,
80
+ bearer_reachable: true,
81
+ browser_session: false,
82
+ idempotent: true,
83
+ has_body: true,
84
+ sandbox: "simulated"
85
+ }.freeze,
86
+ "searchDirectory" => {
87
+ method: "GET",
88
+ path: "/api/directory/search",
89
+ query: [{ name: "q", required: false }, { name: "limit", required: false }],
90
+ scopes: ["directory:search"],
91
+ requires_auth: true,
92
+ bearer_reachable: true,
93
+ browser_session: false,
94
+ idempotent: false,
95
+ has_body: false,
96
+ sandbox: nil
97
+ }.freeze,
98
+ "listResources" => {
99
+ method: "GET",
100
+ path: "/api/resources/list",
101
+ query: [],
102
+ scopes: ["resources:read"],
103
+ requires_auth: true,
104
+ bearer_reachable: true,
105
+ browser_session: false,
106
+ idempotent: false,
107
+ has_body: false,
108
+ sandbox: nil
109
+ }.freeze,
110
+ "bookResource" => {
111
+ method: "POST",
112
+ path: "/api/resources/book",
113
+ query: [],
114
+ scopes: ["resources:book"],
115
+ requires_auth: true,
116
+ bearer_reachable: true,
117
+ browser_session: false,
118
+ idempotent: true,
119
+ has_body: true,
120
+ sandbox: "simulated"
121
+ }.freeze,
122
+ "getMyActivity" => {
123
+ method: "GET",
124
+ path: "/api/activity/me",
125
+ query: [{ name: "limit", required: false }],
126
+ scopes: ["membership:read"],
127
+ requires_auth: true,
128
+ bearer_reachable: true,
129
+ browser_session: false,
130
+ idempotent: false,
131
+ has_body: false,
132
+ sandbox: nil
133
+ }.freeze,
134
+ "getMyLeaderboardStatus" => {
135
+ method: "GET",
136
+ path: "/api/leaderboard/me",
137
+ query: [],
138
+ scopes: ["leaderboard:manage"],
139
+ requires_auth: true,
140
+ bearer_reachable: true,
141
+ browser_session: false,
142
+ idempotent: false,
143
+ has_body: false,
144
+ sandbox: nil
145
+ }.freeze,
146
+ "setLeaderboardOptIn" => {
147
+ method: "POST",
148
+ path: "/api/leaderboard/optin",
149
+ query: [],
150
+ scopes: ["leaderboard:manage"],
151
+ requires_auth: true,
152
+ bearer_reachable: true,
153
+ browser_session: false,
154
+ idempotent: false,
155
+ has_body: true,
156
+ sandbox: "simulated"
157
+ }.freeze,
158
+ "askResearch" => {
159
+ method: "POST",
160
+ path: "/api/research/ask",
161
+ query: [],
162
+ scopes: ["research:query"],
163
+ requires_auth: true,
164
+ bearer_reachable: true,
165
+ browser_session: false,
166
+ idempotent: false,
167
+ has_body: true,
168
+ sandbox: "real"
169
+ }.freeze,
170
+ "submitHighlightPending" => {
171
+ method: "POST",
172
+ path: "/api/ingest/highlights/pending",
173
+ query: [],
174
+ scopes: ["events:submit_recap"],
175
+ requires_auth: true,
176
+ bearer_reachable: true,
177
+ browser_session: false,
178
+ idempotent: true,
179
+ has_body: true,
180
+ sandbox: "simulated"
181
+ }.freeze,
182
+ "submitFeedback" => {
183
+ method: "POST",
184
+ path: "/api/agent/feedback",
185
+ query: [],
186
+ scopes: ["feedback:submit"],
187
+ requires_auth: false,
188
+ bearer_reachable: true,
189
+ browser_session: false,
190
+ idempotent: false,
191
+ has_body: true,
192
+ sandbox: "real"
193
+ }.freeze,
194
+ "revokeOwnToken" => {
195
+ method: "POST",
196
+ path: "/api/agent/token/revoke",
197
+ query: [],
198
+ scopes: [],
199
+ requires_auth: true,
200
+ bearer_reachable: true,
201
+ browser_session: false,
202
+ idempotent: false,
203
+ has_body: false,
204
+ sandbox: "real"
205
+ }.freeze,
206
+ "setupCheck" => {
207
+ method: "GET",
208
+ path: "/api/agent/setup-check",
209
+ query: [],
210
+ scopes: [],
211
+ requires_auth: false,
212
+ bearer_reachable: true,
213
+ browser_session: false,
214
+ idempotent: false,
215
+ has_body: false,
216
+ sandbox: nil
217
+ }.freeze,
218
+ "startSignup" => {
219
+ method: "POST",
220
+ path: "/api/agent/signup/start",
221
+ query: [],
222
+ scopes: [],
223
+ requires_auth: false,
224
+ bearer_reachable: true,
225
+ browser_session: false,
226
+ idempotent: false,
227
+ has_body: true,
228
+ sandbox: nil
229
+ }.freeze,
230
+ "pollSignup" => {
231
+ method: "GET",
232
+ path: "/api/agent/signup/poll",
233
+ query: [{ name: "device_code", required: false }],
234
+ scopes: [],
235
+ requires_auth: false,
236
+ bearer_reachable: true,
237
+ browser_session: false,
238
+ idempotent: false,
239
+ has_body: false,
240
+ sandbox: nil
241
+ }.freeze,
242
+ "getDonorWall" => {
243
+ method: "GET",
244
+ path: "/api/floor10/donations",
245
+ query: [{ name: "limit", required: false }],
246
+ scopes: [],
247
+ requires_auth: false,
248
+ bearer_reachable: true,
249
+ browser_session: false,
250
+ idempotent: false,
251
+ has_body: false,
252
+ sandbox: nil
253
+ }.freeze,
254
+ "getMyTier" => {
255
+ method: "GET",
256
+ path: "/api/tier/me",
257
+ query: [],
258
+ scopes: [],
259
+ requires_auth: true,
260
+ bearer_reachable: false,
261
+ browser_session: true,
262
+ idempotent: false,
263
+ has_body: false,
264
+ sandbox: nil
265
+ }.freeze,
266
+ "requestTier" => {
267
+ method: "POST",
268
+ path: "/api/tier/request",
269
+ query: [],
270
+ scopes: [],
271
+ requires_auth: true,
272
+ bearer_reachable: false,
273
+ browser_session: true,
274
+ idempotent: false,
275
+ has_body: true,
276
+ sandbox: nil
277
+ }.freeze,
278
+ "cancelTierRequest" => {
279
+ method: "DELETE",
280
+ path: "/api/tier/request",
281
+ query: [],
282
+ scopes: [],
283
+ requires_auth: true,
284
+ bearer_reachable: false,
285
+ browser_session: true,
286
+ idempotent: false,
287
+ has_body: false,
288
+ sandbox: nil
289
+ }.freeze,
290
+ }.freeze
291
+ end