|
1 |
+ |
{
|
|
2 |
+ |
"openapi": "3.1.0",
|
|
3 |
+ |
"info": {
|
|
4 |
+ |
"title": "Makenotwork API",
|
|
5 |
+ |
"description": "Creator marketplace API. Only public and stable endpoints are documented.",
|
|
6 |
+ |
"license": {
|
|
7 |
+ |
"name": "PolyForm Noncommercial 1.0.0"
|
|
8 |
+ |
},
|
|
9 |
+ |
"version": "0.11.5"
|
|
10 |
+ |
},
|
|
11 |
+ |
"paths": {
|
|
12 |
+ |
"/api/v1/items/{item_id}/license.txt": {
|
|
13 |
+ |
"get": {
|
|
14 |
+ |
"tags": [
|
|
15 |
+ |
"License Keys"
|
|
16 |
+ |
],
|
|
17 |
+ |
"summary": "Serve rendered license text for an item as plain text.",
|
|
18 |
+ |
"operationId": "license_text",
|
|
19 |
+ |
"parameters": [
|
|
20 |
+ |
{
|
|
21 |
+ |
"name": "item_id",
|
|
22 |
+ |
"in": "path",
|
|
23 |
+ |
"description": "The item ID",
|
|
24 |
+ |
"required": true,
|
|
25 |
+ |
"schema": {
|
|
26 |
+ |
"type": "string"
|
|
27 |
+ |
}
|
|
28 |
+ |
}
|
|
29 |
+ |
],
|
|
30 |
+ |
"responses": {
|
|
31 |
+ |
"200": {
|
|
32 |
+ |
"description": "License text",
|
|
33 |
+ |
"content": {
|
|
34 |
+ |
"text/plain": {}
|
|
35 |
+ |
}
|
|
36 |
+ |
},
|
|
37 |
+ |
"404": {
|
|
38 |
+ |
"description": "Item not found or no license configured"
|
|
39 |
+ |
}
|
|
40 |
+ |
}
|
|
41 |
+ |
}
|
|
42 |
+ |
},
|
|
43 |
+ |
"/api/v1/keys/deactivate": {
|
|
44 |
+ |
"post": {
|
|
45 |
+ |
"tags": [
|
|
46 |
+ |
"License Keys"
|
|
47 |
+ |
],
|
|
48 |
+ |
"summary": "Release an activation slot (user uninstalls).",
|
|
49 |
+ |
"operationId": "deactivate_key",
|
|
50 |
+ |
"requestBody": {
|
|
51 |
+ |
"content": {
|
|
52 |
+ |
"application/json": {
|
|
53 |
+ |
"schema": {
|
|
54 |
+ |
"$ref": "#/components/schemas/DeactivateKeyRequest"
|
|
55 |
+ |
}
|
|
56 |
+ |
}
|
|
57 |
+ |
},
|
|
58 |
+ |
"required": true
|
|
59 |
+ |
},
|
|
60 |
+ |
"responses": {
|
|
61 |
+ |
"200": {
|
|
62 |
+ |
"description": "Deactivation result",
|
|
63 |
+ |
"content": {
|
|
64 |
+ |
"application/json": {
|
|
65 |
+ |
"schema": {
|
|
66 |
+ |
"$ref": "#/components/schemas/DeactivateKeyResponse"
|
|
67 |
+ |
}
|
|
68 |
+ |
}
|
|
69 |
+ |
}
|
|
70 |
+ |
}
|
|
71 |
+ |
}
|
|
72 |
+ |
}
|
|
73 |
+ |
},
|
|
74 |
+ |
"/api/v1/keys/status": {
|
|
75 |
+ |
"post": {
|
|
76 |
+ |
"tags": [
|
|
77 |
+ |
"License Keys"
|
|
78 |
+ |
],
|
|
79 |
+ |
"summary": "Quick validity check without activating, key in the POST body.",
|
|
80 |
+ |
"description": "Prefer this over the GET `/keys/{key_code}/status` form: the key code is a\npurchase-proof secret, and putting it in the URL path leaks it into access\nand proxy logs. This matches the other license endpoints (validate,\ndeactivate, verify), which all carry the key in the body.",
|
|
81 |
+ |
"operationId": "key_status_post",
|
|
82 |
+ |
"requestBody": {
|
|
83 |
+ |
"content": {
|
|
84 |
+ |
"application/json": {
|
|
85 |
+ |
"schema": {
|
|
86 |
+ |
"$ref": "#/components/schemas/KeyStatusRequest"
|
|
87 |
+ |
}
|
|
88 |
+ |
}
|
|
89 |
+ |
},
|
|
90 |
+ |
"required": true
|
|
91 |
+ |
},
|
|
92 |
+ |
"responses": {
|
|
93 |
+ |
"200": {
|
|
94 |
+ |
"description": "Key status",
|
|
95 |
+ |
"content": {
|
|
96 |
+ |
"application/json": {
|
|
97 |
+ |
"schema": {
|
|
98 |
+ |
"$ref": "#/components/schemas/KeyStatusResponse"
|
|
99 |
+ |
}
|
|
100 |
+ |
}
|
|
101 |
+ |
}
|
|
102 |
+ |
}
|
|
103 |
+ |
}
|
|
104 |
+ |
}
|
|
105 |
+ |
},
|
|
106 |
+ |
"/api/v1/keys/validate": {
|
|
107 |
+ |
"post": {
|
|
108 |
+ |
"tags": [
|
|
109 |
+ |
"License Keys"
|
|
110 |
+ |
],
|
|
111 |
+ |
"summary": "Validate a license key and optionally activate it on a machine.",
|
|
112 |
+ |
"operationId": "validate_key",
|
|
113 |
+ |
"requestBody": {
|
|
114 |
+ |
"content": {
|
|
115 |
+ |
"application/json": {
|
|
116 |
+ |
"schema": {
|
|
117 |
+ |
"$ref": "#/components/schemas/ValidateKeyRequest"
|
|
118 |
+ |
}
|
|
119 |
+ |
}
|
|
120 |
+ |
},
|
|
121 |
+ |
"required": true
|
|
122 |
+ |
},
|
|
123 |
+ |
"responses": {
|
|
124 |
+ |
"200": {
|
|
125 |
+ |
"description": "Validation result",
|
|
126 |
+ |
"content": {
|
|
127 |
+ |
"application/json": {
|
|
128 |
+ |
"schema": {
|
|
129 |
+ |
"$ref": "#/components/schemas/ValidateKeyResponse"
|
|
130 |
+ |
}
|
|
131 |
+ |
}
|
|
132 |
+ |
}
|
|
133 |
+ |
}
|
|
134 |
+ |
}
|
|
135 |
+ |
}
|
|
136 |
+ |
},
|
|
137 |
+ |
"/api/v1/keys/{key_code}/status": {
|
|
138 |
+ |
"get": {
|
|
139 |
+ |
"tags": [
|
|
140 |
+ |
"License Keys"
|
|
141 |
+ |
],
|
|
142 |
+ |
"summary": "Quick validity check without activating.",
|
|
143 |
+ |
"description": "DEPRECATED: the key code rides in the URL path, which leaks this\npurchase-proof secret into access/proxy logs. Use `POST /api/v1/keys/status`\n(key in the body) instead. Kept for backward compatibility with SDK\nconsumers that predate the POST form.",
|
|
144 |
+ |
"operationId": "key_status",
|
|
145 |
+ |
"parameters": [
|
|
146 |
+ |
{
|
|
147 |
+ |
"name": "key_code",
|
|
148 |
+ |
"in": "path",
|
|
149 |
+ |
"description": "The license key code",
|
|
150 |
+ |
"required": true,
|
|
151 |
+ |
"schema": {
|
|
152 |
+ |
"type": "string"
|
|
153 |
+ |
}
|
|
154 |
+ |
}
|
|
155 |
+ |
],
|
|
156 |
+ |
"responses": {
|
|
157 |
+ |
"200": {
|
|
158 |
+ |
"description": "Key status (DEPRECATED. Prefer POST /api/v1/keys/status)",
|
|
159 |
+ |
"content": {
|
|
160 |
+ |
"application/json": {
|
|
161 |
+ |
"schema": {
|
|
162 |
+ |
"$ref": "#/components/schemas/KeyStatusResponse"
|
|
163 |
+ |
}
|
|
164 |
+ |
}
|
|
165 |
+ |
}
|
|
166 |
+ |
}
|
|
167 |
+ |
}
|
|
168 |
+ |
}
|
|
169 |
+ |
},
|
|
170 |
+ |
"/api/v1/license/deactivate": {
|
|
171 |
+ |
"post": {
|
|
172 |
+ |
"tags": [
|
|
173 |
+ |
"License Keys"
|
|
174 |
+ |
],
|
|
175 |
+ |
"summary": "Deactivate a license on a specific machine (free up a slot).",
|
|
176 |
+ |
"operationId": "license_deactivate",
|
|
177 |
+ |
"requestBody": {
|
|
178 |
+ |
"content": {
|
|
179 |
+ |
"application/json": {
|
|
180 |
+ |
"schema": {
|
|
181 |
+ |
"$ref": "#/components/schemas/LicenseDeactivateRequest"
|
|
182 |
+ |
}
|
|
183 |
+ |
}
|
|
184 |
+ |
},
|
|
185 |
+ |
"required": true
|
|
186 |
+ |
},
|
|
187 |
+ |
"responses": {
|
|
188 |
+ |
"200": {
|
|
189 |
+ |
"description": "Deactivation result",
|
|
190 |
+ |
"content": {
|
|
191 |
+ |
"application/json": {
|
|
192 |
+ |
"schema": {
|
|
193 |
+ |
"$ref": "#/components/schemas/DeactivateKeyResponse"
|
|
194 |
+ |
}
|
|
195 |
+ |
}
|
|
196 |
+ |
}
|
|
197 |
+ |
}
|
|
198 |
+ |
}
|
|
199 |
+ |
}
|
|
200 |
+ |
},
|
|
201 |
+ |
"/api/v1/license/verify": {
|
|
202 |
+ |
"post": {
|
|
203 |
+ |
"tags": [
|
|
204 |
+ |
"License Keys"
|
|
205 |
+ |
],
|
|
206 |
+ |
"summary": "Verify a license key and bind it to a machine fingerprint.",
|
|
207 |
+ |
"description": "If the project has `license_verification_enabled`, validates the key,\nchecks/creates an activation (using machine_fingerprint as machine_id),\nand returns a signed JWT for offline verification (valid 7 days).",
|
|
208 |
+ |
"operationId": "license_verify",
|
|
209 |
+ |
"requestBody": {
|
|
210 |
+ |
"content": {
|
|
211 |
+ |
"application/json": {
|
|
212 |
+ |
"schema": {
|
|
213 |
+ |
"$ref": "#/components/schemas/LicenseVerifyRequest"
|
|
214 |
+ |
}
|
|
215 |
+ |
}
|
|
216 |
+ |
},
|
|
217 |
+ |
"required": true
|
|
218 |
+ |
},
|
|
219 |
+ |
"responses": {
|
|
220 |
+ |
"200": {
|
|
221 |
+ |
"description": "Verification result with optional offline JWT",
|
|
222 |
+ |
"content": {
|
|
223 |
+ |
"application/json": {
|
|
224 |
+ |
"schema": {
|
|
225 |
+ |
"$ref": "#/components/schemas/LicenseVerifyResponse"
|
|
226 |
+ |
}
|
|
227 |
+ |
}
|
|
228 |
+ |
}
|
|
229 |
+ |
}
|
|
230 |
+ |
}
|
|
231 |
+ |
}
|
|
232 |
+ |
},
|
|
233 |
+ |
"/api/v1/sync/account": {
|
|
234 |
+ |
"get": {
|
|
235 |
+ |
"tags": [
|
|
236 |
+ |
"SyncKit"
|
|
237 |
+ |
],
|
|
238 |
+ |
"summary": "Return the authenticated user's email and username, for the app to display\n\"logged in as ...\" in its sync UI.",
|
|
239 |
+ |
"operationId": "sync_account",
|
|
240 |
+ |
"responses": {
|
|
241 |
+ |
"200": {
|
|
242 |
+ |
"description": "Account info",
|
|
243 |
+ |
"content": {
|
|
244 |
+ |
"application/json": {
|
|
245 |
+ |
"schema": {
|
|
246 |
+ |
"$ref": "#/components/schemas/SyncAccountResponse"
|
|
247 |
+ |
}
|
|
248 |
+ |
}
|
|
249 |
+ |
}
|
|
250 |
+ |
}
|
|
251 |
+ |
},
|
|
252 |
+ |
"security": [
|
|
253 |
+ |
{
|
|
254 |
+ |
"bearer": []
|
|
255 |
+ |
}
|
|
256 |
+ |
]
|
|
257 |
+ |
}
|
|
258 |
+ |
},
|
|
259 |
+ |
"/api/v1/sync/app/pricing": {
|
|
260 |
+ |
"post": {
|
|
261 |
+ |
"tags": [
|
|
262 |
+ |
"SyncKit"
|
|
263 |
+ |
],
|
|
264 |
+ |
"summary": "Return the pricing-formula constants for an app. The client uses these to\nquote a price locally as the user adjusts the cap slider; the same formula\nis enforced server-side at checkout so the client number is only advisory.",
|
|
265 |
+ |
"operationId": "get_app_pricing",
|
|
266 |
+ |
"requestBody": {
|
|
267 |
+ |
"content": {
|
|
268 |
+ |
"application/json": {
|
|
269 |
+ |
"schema": {
|
|
270 |
+ |
"$ref": "#/components/schemas/AppPricingRequest"
|
|
271 |
+ |
}
|
|
272 |
+ |
}
|
|
273 |
+ |
},
|
|
274 |
+ |
"required": true
|
|
275 |
+ |
},
|
|
276 |
+ |
"responses": {
|
|
277 |
+ |
"200": {
|
|
278 |
+ |
"description": "Pricing formula",
|
|
279 |
+ |
"content": {
|
|
280 |
+ |
"application/json": {
|
|
281 |
+ |
"schema": {
|
|
282 |
+ |
"$ref": "#/components/schemas/AppPricingResponse"
|
|
283 |
+ |
}
|
|
284 |
+ |
}
|
|
285 |
+ |
}
|
|
286 |
+ |
}
|
|
287 |
+ |
}
|
|
288 |
+ |
}
|
|
289 |
+ |
},
|
|
290 |
+ |
"/api/v1/sync/auth": {
|
|
291 |
+ |
"post": {
|
|
292 |
+ |
"tags": [
|
|
293 |
+ |
"SyncKit"
|
|
294 |
+ |
],
|
|
295 |
+ |
"summary": "Authenticate a user and return a JWT for subsequent sync API calls.",
|
|
296 |
+ |
"description": "Verifies the app API key, then validates user email/password credentials.\nReturns a short-lived JWT containing the user ID and app ID, which the\nclient SDK includes as a Bearer token on all other sync endpoints.",
|
|
297 |
+ |
"operationId": "sync_auth",
|
|
298 |
+ |
"requestBody": {
|
|
299 |
+ |
"content": {
|
|
300 |
+ |
"application/json": {
|
|
301 |
+ |
"schema": {
|
|
302 |
+ |
"$ref": "#/components/schemas/SyncAuthRequest"
|
|
303 |
+ |
}
|
|
304 |
+ |
}
|
|
305 |
+ |
},
|
|
306 |
+ |
"required": true
|
|
307 |
+ |
},
|
|
308 |
+ |
"responses": {
|
|
309 |
+ |
"200": {
|
|
310 |
+ |
"description": "JWT token for sync API access",
|
|
311 |
+ |
"content": {
|
|
312 |
+ |
"application/json": {
|
|
313 |
+ |
"schema": {
|
|
314 |
+ |
"$ref": "#/components/schemas/SyncAuthResponse"
|
|
315 |
+ |
}
|
|
316 |
+ |
}
|
|
317 |
+ |
}
|
|
318 |
+ |
},
|
|
319 |
+ |
"401": {
|
|
320 |
+ |
"description": "Invalid credentials or API key"
|
|
321 |
+ |
}
|
|
322 |
+ |
}
|
|
323 |
+ |
}
|
|
324 |
+ |
},
|
|
325 |
+ |
"/api/v1/sync/blobs/confirm": {
|
|
326 |
+ |
"post": {
|
|
327 |
+ |
"tags": [
|
|
328 |
+ |
"SyncKit"
|
|
329 |
+ |
],
|
|
330 |
+ |
"summary": "Confirm that a blob upload to S3 completed successfully.",
|
|
331 |
+ |
"description": "Verifies the object exists in S3, then records it in the database.\nIdempotent: returns success without creating a duplicate.\n\nContent-addressing trust model (ultra-fuzz Run 4 Storage NOTE, decision\n2026-06-23; revised 2026-07-21): the blob `hash` is treated as a\ncontent-address LABEL, confirm reads the authoritative `object_size` from S3\nbut does not re-hash the bytes to prove they match `hash`. The blast radius\nis per-user only: the key is `{app_id}/{user_id}/{hash}` and storage is\n`UNIQUE(app_id, user_id, hash)`, so a client that stores mismatched bytes can\npoison only its OWN dedup namespace, no cross-user effect, no data exposure.\n\nThis note used to say the A+ fix was binding `x-amz-checksum-sha256` into the\npresigned PUT so S3 rejects a mismatched upload at write time. That reasoning\ndoes not hold for these blobs, and the correction is worth keeping: the stored\nobject is E2E *ciphertext* sealed with random per-chunk nonces, while `hash`\nis the SHA-256 of the *plaintext*. The server never sees plaintext, so it\ncannot derive the expected ciphertext digest at presign time, any checksum it\nbinds has to come from the client, i.e. the party whose honesty was in\nquestion. Checksum binding (which the multipart path now does per part) buys\ntransport integrity, not content-address enforcement.\n\nWhat actually binds the bytes to the address is the AEAD: each chunk is sealed\nwith `(hash, chunk_index, chunk_count)` as associated data, so ciphertext that\nopens under `hash` is cryptographically tied to it, and the client re-hashes\nthe plaintext after decrypting. A client storing mismatched bytes breaks only\nits own blob. Server-side re-hashing would cost a full object download per\nconfirm to defend a client against itself, which is why it is not done.",
|
|
332 |
+ |
"operationId": "blob_confirm_upload",
|
|
333 |
+ |
"requestBody": {
|
|
334 |
+ |
"content": {
|
|
335 |
+ |
"application/json": {
|
|
336 |
+ |
"schema": {
|
|
337 |
+ |
"$ref": "#/components/schemas/BlobConfirmRequest"
|
|
338 |
+ |
}
|
|
339 |
+ |
}
|
|
340 |
+ |
},
|
|
341 |
+ |
"required": true
|
|
342 |
+ |
},
|
|
343 |
+ |
"responses": {
|
|
344 |
+ |
"204": {
|
|
345 |
+ |
"description": "Upload confirmed"
|
|
346 |
+ |
}
|
|
347 |
+ |
},
|
|
348 |
+ |
"security": [
|
|
349 |
+ |
{
|
|
350 |
+ |
"bearer": []
|
|
351 |
+ |
}
|
|
352 |
+ |
]
|
|
353 |
+ |
}
|
|
354 |
+ |
},
|
|
355 |
+ |
"/api/v1/sync/blobs/download": {
|
|
356 |
+ |
"post": {
|
|
357 |
+ |
"tags": [
|
|
358 |
+ |
"SyncKit"
|
|
359 |
+ |
],
|
|
360 |
+ |
"summary": "Request a pre-signed S3 download URL for a blob by hash.",
|
|
361 |
+ |
"operationId": "blob_download_url",
|
|
362 |
+ |
"requestBody": {
|
|
363 |
+ |
"content": {
|
|
364 |
+ |
"application/json": {
|
|
365 |
+ |
"schema": {
|
|
366 |
+ |
"$ref": "#/components/schemas/BlobDownloadUrlRequest"
|
|
367 |
+ |
}
|
|
368 |
+ |
}
|
|
369 |
+ |
},
|
|
370 |
+ |
"required": true
|
|
371 |
+ |
},
|
|
372 |
+ |
"responses": {
|
|
373 |
+ |
"200": {
|
|
374 |
+ |
"description": "Pre-signed download URL",
|
|
375 |
+ |
"content": {
|
|
376 |
+ |
"application/json": {
|
|
377 |
+ |
"schema": {
|
|
378 |
+ |
"$ref": "#/components/schemas/BlobDownloadUrlResponse"
|
|
379 |
+ |
}
|
|
380 |
+ |
}
|
|
381 |
+ |
}
|
|
382 |
+ |
},
|
|
383 |
+ |
"404": {
|
|
384 |
+ |
"description": "Blob not found"
|
|
385 |
+ |
}
|
|
386 |
+ |
},
|
|
387 |
+ |
"security": [
|
|
388 |
+ |
{
|
|
389 |
+ |
"bearer": []
|
|
390 |
+ |
}
|
|
391 |
+ |
]
|
|
392 |
+ |
}
|
|
393 |
+ |
},
|
|
394 |
+ |
"/api/v1/sync/blobs/multipart/abort": {
|
|
395 |
+ |
"post": {
|
|
396 |
+ |
"tags": [
|
|
397 |
+ |
"SyncKit"
|
|
398 |
+ |
],
|
|
399 |
+ |
"summary": "Release the parts of an abandoned session (client cancel).",
|
|
400 |
+ |
"description": "Incomplete multipart uploads bill for their parts until aborted, so a client\nthat cleans up on cancel is the cheapest fix; the orphan reaper is the\nbackstop for clients that vanish.",
|
|
401 |
+ |
"operationId": "blob_multipart_abort",
|
|
402 |
+ |
"requestBody": {
|
|
403 |
+ |
"content": {
|
|
404 |
+ |
"application/json": {
|
|
405 |
+ |
"schema": {
|
|
406 |
+ |
"$ref": "#/components/schemas/BlobMultipartAbortRequest"
|
|
407 |
+ |
}
|
|
408 |
+ |
}
|
|
409 |
+ |
},
|
|
410 |
+ |
"required": true
|
|
411 |
+ |
},
|
|
412 |
+ |
"responses": {
|
|
413 |
+ |
"204": {
|
|
414 |
+ |
"description": "Session aborted"
|
|
415 |
+ |
}
|
|
416 |
+ |
},
|
|
417 |
+ |
"security": [
|
|
418 |
+ |
{
|
|
419 |
+ |
"bearer": []
|
|
420 |
+ |
}
|
|
421 |
+ |
]
|
|
422 |
+ |
}
|
|
423 |
+ |
},
|
|
424 |
+ |
"/api/v1/sync/blobs/multipart/complete": {
|
|
425 |
+ |
"post": {
|
|
426 |
+ |
"tags": [
|
|
427 |
+ |
"SyncKit"
|
|
428 |
+ |
],
|
|
429 |
+ |
"summary": "Assemble the uploaded parts into the blob object.",
|
|
430 |
+ |
"description": "Transport only: the client then calls `/blobs/confirm`, which reads the real\nobject size from S3 and applies every quota and billing rule.",
|
|
431 |
+ |
"operationId": "blob_multipart_complete",
|
|
432 |
+ |
"requestBody": {
|
|
433 |
+ |
"content": {
|
|
434 |
+ |
"application/json": {
|
|
435 |
+ |
"schema": {
|
|
436 |
+ |
"$ref": "#/components/schemas/BlobMultipartCompleteRequest"
|
|
437 |
+ |
}
|
|
438 |
+ |
}
|
|
439 |
+ |
},
|
|
440 |
+ |
"required": true
|
|
441 |
+ |
},
|
|
442 |
+ |
"responses": {
|
|
443 |
+ |
"204": {
|
|
444 |
+ |
"description": "Parts assembled"
|
|
445 |
+ |
}
|
|
446 |
+ |
},
|
|
447 |
+ |
"security": [
|
|
448 |
+ |
{
|
|
449 |
+ |
"bearer": []
|
|
450 |
+ |
}
|
|
451 |
+ |
]
|
|
452 |
+ |
}
|
|
453 |
+ |
},
|
|
454 |
+ |
"/api/v1/sync/blobs/multipart/parts": {
|
|
455 |
+ |
"post": {
|
|
456 |
+ |
"tags": [
|
|
457 |
+ |
"SyncKit"
|
|
458 |
+ |
],
|
|
459 |
+ |
"summary": "Mint a bounded window of presigned `UploadPart` URLs, each carrying its exact\nsigned `Content-Length`, the same defense-in-depth the one-shot presign\napplies.",
|
|
460 |
+ |
"operationId": "blob_multipart_parts",
|
|
461 |
+ |
"requestBody": {
|
|
462 |
+ |
"content": {
|
|
463 |
+ |
"application/json": {
|
|
464 |
+ |
"schema": {
|
|
465 |
+ |
"$ref": "#/components/schemas/BlobMultipartPartsRequest"
|
|
466 |
+ |
}
|
|
467 |
+ |
}
|
|
468 |
+ |
},
|
|
469 |
+ |
"required": true
|
|
470 |
+ |
},
|
|
471 |
+ |
"responses": {
|
|
472 |
+ |
"200": {
|
|
473 |
+ |
"description": "Presigned part URLs",
|
|
474 |
+ |
"content": {
|
|
475 |
+ |
"application/json": {
|
|
476 |
+ |
"schema": {
|
|
477 |
+ |
"$ref": "#/components/schemas/BlobMultipartPartsResponse"
|
|
478 |
+ |
}
|
|
479 |
+ |
}
|
|
480 |
+ |
}
|
|
481 |
+ |
}
|
|
482 |
+ |
},
|
|
483 |
+ |
"security": [
|
|
484 |
+ |
{
|
|
485 |
+ |
"bearer": []
|
|
486 |
+ |
}
|
|
487 |
+ |
]
|
|
488 |
+ |
}
|
|
489 |
+ |
},
|
|
490 |
+ |
"/api/v1/sync/blobs/multipart/start": {
|
|
491 |
+ |
"post": {
|
|
492 |
+ |
"tags": [
|
|
493 |
+ |
"SyncKit"
|
|
494 |
+ |
],
|
|
495 |
+ |
"summary": "Open a multipart upload session for a large blob.",
|
|
496 |
+ |
"description": "`size_bytes` is the ciphertext length, which the client derives from the\nplaintext length alone (`blob_encrypted_len`) before sealing anything. The\npart geometry is pure arithmetic over it, so both sides compute identical\nboundaries without a round trip.",
|
|
497 |
+ |
"operationId": "blob_multipart_start",
|
|
498 |
+ |
"requestBody": {
|
|
499 |
+ |
"content": {
|
|
500 |
+ |
"application/json": {
|