@spicyapi/sdk 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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -0
  3. package/SECURITY.md +4 -0
  4. package/contracts/openapi.yaml +752 -0
  5. package/dist/src/docs/index.d.ts +10 -0
  6. package/dist/src/docs/index.d.ts.map +1 -0
  7. package/dist/src/docs/index.js +181 -0
  8. package/dist/src/docs/index.js.map +1 -0
  9. package/dist/src/generated/openapi.d.ts +888 -0
  10. package/dist/src/generated/openapi.d.ts.map +1 -0
  11. package/dist/src/generated/openapi.js +6 -0
  12. package/dist/src/generated/openapi.js.map +1 -0
  13. package/dist/src/index.d.ts +6 -0
  14. package/dist/src/index.d.ts.map +1 -0
  15. package/dist/src/index.js +5 -0
  16. package/dist/src/index.js.map +1 -0
  17. package/dist/src/openapi.d.ts +2 -0
  18. package/dist/src/openapi.d.ts.map +1 -0
  19. package/dist/src/openapi.js +18 -0
  20. package/dist/src/openapi.js.map +1 -0
  21. package/dist/src/sdk/client.d.ts +41 -0
  22. package/dist/src/sdk/client.d.ts.map +1 -0
  23. package/dist/src/sdk/client.js +407 -0
  24. package/dist/src/sdk/client.js.map +1 -0
  25. package/dist/src/sdk/errors.d.ts +36 -0
  26. package/dist/src/sdk/errors.d.ts.map +1 -0
  27. package/dist/src/sdk/errors.js +53 -0
  28. package/dist/src/sdk/errors.js.map +1 -0
  29. package/dist/src/sdk/index.d.ts +4 -0
  30. package/dist/src/sdk/index.d.ts.map +1 -0
  31. package/dist/src/sdk/index.js +3 -0
  32. package/dist/src/sdk/index.js.map +1 -0
  33. package/dist/src/sdk/types.d.ts +73 -0
  34. package/dist/src/sdk/types.d.ts.map +1 -0
  35. package/dist/src/sdk/types.js +2 -0
  36. package/dist/src/sdk/types.js.map +1 -0
  37. package/dist/src/webhooks/index.d.ts +28 -0
  38. package/dist/src/webhooks/index.d.ts.map +1 -0
  39. package/dist/src/webhooks/index.js +83 -0
  40. package/dist/src/webhooks/index.js.map +1 -0
  41. package/package.json +49 -0
@@ -0,0 +1,752 @@
1
+ openapi: 3.1.0
2
+ info:
3
+ title: SpicyAPI Public API
4
+ version: 1.0.0
5
+ description: |
6
+ Contract for the implemented `/api/v1` integration surface.
7
+
8
+ Every operation requires a Bearer API key. Every JSON response uses the
9
+ `{code,msg,data,request_id}` envelope. A successful HTTP response has
10
+ `code: 200`; asynchronous task success or failure is expressed by
11
+ `data.state`, not by the envelope code.
12
+ servers:
13
+ - url: https://api.spicyapi.ai
14
+ description: Production
15
+ tags:
16
+ - name: Tasks
17
+ - name: Models
18
+ - name: Account
19
+ - name: Media
20
+ security:
21
+ - bearerAuth: []
22
+ paths:
23
+ /api/v1/jobs/createTask:
24
+ post:
25
+ tags: [Tasks]
26
+ operationId: createTask
27
+ summary: Create an asynchronous generation task
28
+ description: |
29
+ Validates the model input, reserves the estimated charge, chooses an
30
+ available deployment, and creates a task in `queued` state. HTTP 202
31
+ means the task was accepted; it does not mean generation has completed.
32
+
33
+ An `Idempotency-Key` is optional but strongly recommended. For the same
34
+ account **and the same API key**, repeating a key with the same normalized
35
+ model, input, callback URL, and mature flag within 24 hours returns the
36
+ original task and does not reserve funds twice. A sibling API key cannot
37
+ replay the mapping: it receives `409 Conflict` without the original task
38
+ ID. Reusing the key with different request semantics also returns
39
+ `409 Conflict`. Callbacks are optional and are validated against
40
+ private/loopback destinations before acceptance.
41
+ parameters:
42
+ - $ref: '#/components/parameters/IdempotencyKey'
43
+ requestBody:
44
+ required: true
45
+ content:
46
+ application/json:
47
+ schema:
48
+ $ref: '#/components/schemas/CreateTaskRequest'
49
+ responses:
50
+ '202':
51
+ description: A newly accepted task is returned in `queued` state. A same-key idempotent replay returns the original task in its current state and does not create another hold. The hold is not a charge.
52
+ headers:
53
+ Location:
54
+ description: Relative URL of the task status resource.
55
+ schema: { type: string }
56
+ Retry-After: { $ref: '#/components/headers/RetryAfter' }
57
+ Cache-Control:
58
+ description: Task submissions contain caller-specific state and must not be cached.
59
+ schema: { type: string, const: no-store }
60
+ X-RateLimit-Limit: { $ref: '#/components/headers/RateLimitLimit' }
61
+ X-RateLimit-Remaining: { $ref: '#/components/headers/RateLimitRemaining' }
62
+ content:
63
+ application/json:
64
+ schema:
65
+ $ref: '#/components/schemas/CreateTaskEnvelope'
66
+ '400': { $ref: '#/components/responses/BadRequest' }
67
+ '401': { $ref: '#/components/responses/Unauthorized' }
68
+ '402': { $ref: '#/components/responses/PaymentRequired' }
69
+ '403': { $ref: '#/components/responses/Forbidden' }
70
+ '409': { $ref: '#/components/responses/Conflict' }
71
+ '413': { $ref: '#/components/responses/PayloadTooLarge' }
72
+ '429': { $ref: '#/components/responses/RateLimited' }
73
+ '500': { $ref: '#/components/responses/ServerError' }
74
+ '503': { $ref: '#/components/responses/Unavailable' }
75
+ callbacks:
76
+ onTaskTerminal:
77
+ '{$request.body#/callBackUrl}':
78
+ post:
79
+ summary: Deliver a terminal task record
80
+ description: |
81
+ Sent only when `callBackUrl` was supplied. New integrations use
82
+ payload version 2, whose body is the same envelope and task
83
+ record returned by `recordInfo`. Payload version 1 remains a
84
+ compatibility output and is selected by the version header.
85
+
86
+ Verify `X-Webhook-Signature` over
87
+ `taskId.timestamp.sha256(raw_body)` with HMAC-SHA256 and the
88
+ account webhook key. Select `taskId` from top-level `task_id`
89
+ for payload v1 and from `data.taskId` for payload v2, then
90
+ compare the Base64-encoded result in constant time.
91
+ Reject stale timestamps. A delivery may be retried and receivers
92
+ must be idempotent; in v2, `request_id` is the stable delivery ID.
93
+ parameters:
94
+ - name: X-Webhook-Timestamp
95
+ in: header
96
+ required: true
97
+ schema: { type: string, pattern: '^[0-9]+$' }
98
+ description: Unix timestamp used by the signature.
99
+ - name: X-Webhook-Signature
100
+ in: header
101
+ required: true
102
+ schema: { type: string }
103
+ description: Base64-encoded HMAC-SHA256 signature.
104
+ - name: X-Webhook-Payload-Version
105
+ in: header
106
+ required: true
107
+ schema: { type: integer, enum: [1, 2] }
108
+ requestBody:
109
+ required: true
110
+ content:
111
+ application/json:
112
+ schema:
113
+ oneOf:
114
+ - $ref: '#/components/schemas/TaskRecordEnvelope'
115
+ - $ref: '#/components/schemas/LegacyWebhookPayload'
116
+ responses:
117
+ '200':
118
+ description: Any 2xx response acknowledges delivery.
119
+ /api/v1/jobs/recordInfo:
120
+ get:
121
+ tags: [Tasks]
122
+ operationId: recordInfo
123
+ summary: Get a task created by the authenticated API key
124
+ parameters:
125
+ - name: taskId
126
+ in: query
127
+ required: true
128
+ schema: { type: string, minLength: 1 }
129
+ responses:
130
+ '200':
131
+ description: Current task record. Terminal and non-terminal states share this shape.
132
+ headers:
133
+ Cache-Control:
134
+ description: Task state is caller-specific and changes asynchronously.
135
+ schema: { type: string, const: no-store }
136
+ content:
137
+ application/json:
138
+ schema:
139
+ $ref: '#/components/schemas/TaskRecordEnvelope'
140
+ '400': { $ref: '#/components/responses/BadRequest' }
141
+ '401': { $ref: '#/components/responses/Unauthorized' }
142
+ '403': { $ref: '#/components/responses/Forbidden' }
143
+ '404':
144
+ description: Task does not exist or belongs to another account or API key; these cases are indistinguishable.
145
+ headers:
146
+ Cache-Control:
147
+ schema: { type: string, const: no-store }
148
+ content:
149
+ application/json:
150
+ schema: { $ref: '#/components/schemas/ErrorEnvelope' }
151
+ '429': { $ref: '#/components/responses/RateLimited' }
152
+ '500': { $ref: '#/components/responses/ServerError' }
153
+ /api/v1/jobs/retry:
154
+ post:
155
+ tags: [Tasks]
156
+ operationId: retryTask
157
+ summary: Create a new task from a failed or expired task
158
+ description: |
159
+ The source terminal task is never modified. Current model schema,
160
+ pricing, permissions, balance, and routing are evaluated again. An
161
+ optional Idempotency-Key is scoped to the source task and retry action.
162
+ The source task must have been created by the same API key; unknown,
163
+ cross-account, and same-account cross-key task IDs all return 404.
164
+ parameters:
165
+ - $ref: '#/components/parameters/IdempotencyKey'
166
+ requestBody:
167
+ required: true
168
+ content:
169
+ application/json:
170
+ schema: { $ref: '#/components/schemas/TaskActionRequest' }
171
+ responses:
172
+ '200':
173
+ description: A newly created retry task is returned in `queued` state. An idempotent replay returns that retry task in its current state.
174
+ content:
175
+ application/json:
176
+ schema: { $ref: '#/components/schemas/RetryTaskEnvelope' }
177
+ '400': { $ref: '#/components/responses/BadRequest' }
178
+ '401': { $ref: '#/components/responses/Unauthorized' }
179
+ '402': { $ref: '#/components/responses/PaymentRequired' }
180
+ '403': { $ref: '#/components/responses/Forbidden' }
181
+ '404': { $ref: '#/components/responses/NotFound' }
182
+ '409': { $ref: '#/components/responses/Conflict' }
183
+ '413': { $ref: '#/components/responses/PayloadTooLarge' }
184
+ '429': { $ref: '#/components/responses/RateLimited' }
185
+ '500': { $ref: '#/components/responses/ServerError' }
186
+ '503': { $ref: '#/components/responses/Unavailable' }
187
+ /api/v1/models:
188
+ get:
189
+ tags: [Models]
190
+ operationId: listModels
191
+ summary: List callable models and account-specific prices
192
+ description: |
193
+ Returns all enabled models without pagination. The response is private
194
+ because prices include the authenticated account's group multiplier.
195
+ The server sends `Cache-Control: private, max-age=60`.
196
+ parameters:
197
+ - name: modality
198
+ in: query
199
+ schema: { type: string, enum: [image, video, audio, text] }
200
+ - name: provider
201
+ in: query
202
+ schema: { type: string }
203
+ - name: task
204
+ in: query
205
+ schema: { type: string }
206
+ description: Exact supported task, for example `text-to-image`.
207
+ - name: search
208
+ in: query
209
+ schema: { type: string, maxLength: 128 }
210
+ - name: includeSchema
211
+ in: query
212
+ schema:
213
+ oneOf:
214
+ - { type: boolean }
215
+ - { type: integer, enum: [0, 1] }
216
+ description: Include each model's input JSON Schema when `true` or `1`.
217
+ - name: includeExamples
218
+ in: query
219
+ schema:
220
+ oneOf:
221
+ - { type: boolean }
222
+ - { type: integer, enum: [0, 1] }
223
+ description: Include validated example inputs when `true` or `1`.
224
+ responses:
225
+ '200':
226
+ description: Model list.
227
+ headers:
228
+ Cache-Control:
229
+ schema: { type: string, const: 'private, max-age=60' }
230
+ content:
231
+ application/json:
232
+ schema:
233
+ $ref: '#/components/schemas/ModelListEnvelope'
234
+ '400': { $ref: '#/components/responses/BadRequest' }
235
+ '401': { $ref: '#/components/responses/Unauthorized' }
236
+ '403': { $ref: '#/components/responses/Forbidden' }
237
+ '429': { $ref: '#/components/responses/RateLimited' }
238
+ '500': { $ref: '#/components/responses/ServerError' }
239
+ /api/v1/models/{model}:
240
+ get:
241
+ tags: [Models]
242
+ operationId: getModel
243
+ summary: Get one model, including its input JSON Schema
244
+ description: |
245
+ `model` is the exact value used by `createTask.model` and may contain
246
+ slashes. URL-encode embedded slashes when using clients that treat path
247
+ parameters as one segment (for example `bytedance%2Fseedance-2-fast`).
248
+ parameters:
249
+ - name: model
250
+ in: path
251
+ required: true
252
+ allowReserved: true
253
+ schema: { type: string, minLength: 1 }
254
+ responses:
255
+ '200':
256
+ description: Complete model record.
257
+ headers:
258
+ Cache-Control:
259
+ schema: { type: string, const: 'private, max-age=60' }
260
+ content:
261
+ application/json:
262
+ schema:
263
+ $ref: '#/components/schemas/ModelEnvelope'
264
+ '400': { $ref: '#/components/responses/BadRequest' }
265
+ '401': { $ref: '#/components/responses/Unauthorized' }
266
+ '403': { $ref: '#/components/responses/Forbidden' }
267
+ '404': { $ref: '#/components/responses/NotFound' }
268
+ '429': { $ref: '#/components/responses/RateLimited' }
269
+ '500': { $ref: '#/components/responses/ServerError' }
270
+ /api/v1/chat/credit:
271
+ get:
272
+ tags: [Account]
273
+ operationId: getCreditBalance
274
+ summary: Get the account balance
275
+ responses:
276
+ '200':
277
+ description: Available, held, and total balance in USD decimal strings.
278
+ content:
279
+ application/json:
280
+ schema:
281
+ $ref: '#/components/schemas/BalanceEnvelope'
282
+ '401': { $ref: '#/components/responses/Unauthorized' }
283
+ '403': { $ref: '#/components/responses/Forbidden' }
284
+ '429': { $ref: '#/components/responses/RateLimited' }
285
+ '500': { $ref: '#/components/responses/ServerError' }
286
+ /api/v1/common/upload-url:
287
+ post:
288
+ tags: [Media]
289
+ operationId: createUploadUrl
290
+ summary: Create a presigned image upload ticket
291
+ description: |
292
+ The server generates the object key. Upload the exact declared number of
293
+ bytes with PUT and copy every returned header. Supported media types are
294
+ JPEG, PNG, WebP, and GIF; the default maximum is 10 MiB. After PUT,
295
+ call the file commit endpoint and use its spicy:// URI in task input.
296
+ requestBody:
297
+ required: true
298
+ content:
299
+ application/json:
300
+ schema:
301
+ $ref: '#/components/schemas/UploadURLRequest'
302
+ responses:
303
+ '200':
304
+ description: Presigned upload ticket.
305
+ content:
306
+ application/json:
307
+ schema:
308
+ $ref: '#/components/schemas/UploadURLEnvelope'
309
+ '400': { $ref: '#/components/responses/BadRequest' }
310
+ '401': { $ref: '#/components/responses/Unauthorized' }
311
+ '403': { $ref: '#/components/responses/Forbidden' }
312
+ '413': { $ref: '#/components/responses/PayloadTooLarge' }
313
+ '429': { $ref: '#/components/responses/RateLimited' }
314
+ '500': { $ref: '#/components/responses/ServerError' }
315
+ /api/v1/files/{fileId}/commit:
316
+ post:
317
+ tags: [Media]
318
+ operationId: commitUploadedFile
319
+ summary: Commit a completed direct upload
320
+ description: |
321
+ Compares the stored byte count and media type with the ticket, checks
322
+ the image signature, computes SHA-256, and copies the temporary object
323
+ to an immutable private key. Only the returned spicy:// URI can be used
324
+ in task input. Repeating a successful commit is idempotent.
325
+ parameters:
326
+ - name: fileId
327
+ in: path
328
+ required: true
329
+ schema: { type: string, pattern: '^fil_' }
330
+ responses:
331
+ '200':
332
+ description: Ready tenant-scoped file reference.
333
+ content:
334
+ application/json:
335
+ schema: { $ref: '#/components/schemas/FileCommitEnvelope' }
336
+ '400': { $ref: '#/components/responses/BadRequest' }
337
+ '401': { $ref: '#/components/responses/Unauthorized' }
338
+ '403': { $ref: '#/components/responses/Forbidden' }
339
+ '404': { $ref: '#/components/responses/NotFound' }
340
+ '409': { $ref: '#/components/responses/Conflict' }
341
+ '429': { $ref: '#/components/responses/RateLimited' }
342
+ '500': { $ref: '#/components/responses/ServerError' }
343
+ /api/v1/common/download-url:
344
+ post:
345
+ tags: [Media]
346
+ operationId: createDownloadUrl
347
+ summary: Create a short-lived task output download URL
348
+ description: |
349
+ The server verifies both task ownership and that the requested key is an
350
+ output of that task. Leave `key` empty to select the first output.
351
+ requestBody:
352
+ required: true
353
+ content:
354
+ application/json:
355
+ schema:
356
+ $ref: '#/components/schemas/DownloadURLRequest'
357
+ responses:
358
+ '200':
359
+ description: Short-lived download ticket.
360
+ content:
361
+ application/json:
362
+ schema:
363
+ $ref: '#/components/schemas/DownloadURLEnvelope'
364
+ '400': { $ref: '#/components/responses/BadRequest' }
365
+ '401': { $ref: '#/components/responses/Unauthorized' }
366
+ '403': { $ref: '#/components/responses/Forbidden' }
367
+ '404': { $ref: '#/components/responses/NotFound' }
368
+ '409': { $ref: '#/components/responses/Conflict' }
369
+ '413': { $ref: '#/components/responses/PayloadTooLarge' }
370
+ '429': { $ref: '#/components/responses/RateLimited' }
371
+ '500': { $ref: '#/components/responses/ServerError' }
372
+ components:
373
+ securitySchemes:
374
+ bearerAuth:
375
+ type: http
376
+ scheme: bearer
377
+ bearerFormat: 'sk-spicy-<48 lowercase hex characters>'
378
+ description: 'Send `Authorization: Bearer sk-spicy-...`. Keep keys server-side.'
379
+ parameters:
380
+ IdempotencyKey:
381
+ name: Idempotency-Key
382
+ in: header
383
+ required: false
384
+ description: Stable key for one logical task submission; retained for 24 hours per account and bound to both the submitting API key and normalized request fingerprint. A sibling key receives 409 without the original task ID.
385
+ schema: { type: string, minLength: 1, maxLength: 128 }
386
+ headers:
387
+ RateLimitLimit:
388
+ description: Capacity of the active account-level rate-limit window.
389
+ schema: { type: integer }
390
+ RateLimitRemaining:
391
+ description: Remaining requests in the active window.
392
+ schema: { type: integer }
393
+ RetryAfter:
394
+ description: Seconds to wait before retrying.
395
+ schema: { type: integer, minimum: 1 }
396
+ responses:
397
+ BadRequest:
398
+ description: Invalid JSON, query, callback URL, model input, or media declaration.
399
+ content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
400
+ Unauthorized:
401
+ description: Missing, malformed, expired, revoked, or otherwise invalid API key.
402
+ content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
403
+ PaymentRequired:
404
+ description: Insufficient balance (`40201`) or a spend cap was reached (`40202`).
405
+ content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
406
+ Forbidden:
407
+ description: Account, model, API-key, IP, or region authorization failed.
408
+ content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
409
+ NotFound:
410
+ description: Resource does not exist or is not visible to this account and API key. These cases are intentionally indistinguishable.
411
+ content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
412
+ Conflict:
413
+ description: Idempotency or media-transfer state conflicts with this request.
414
+ content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
415
+ PayloadTooLarge:
416
+ description: Request body exceeds the server's accepted size limit.
417
+ content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
418
+ RateLimited:
419
+ description: Account-level request limit exceeded.
420
+ headers:
421
+ X-RateLimit-Limit: { $ref: '#/components/headers/RateLimitLimit' }
422
+ X-RateLimit-Remaining: { $ref: '#/components/headers/RateLimitRemaining' }
423
+ Retry-After: { $ref: '#/components/headers/RetryAfter' }
424
+ content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
425
+ ServerError:
426
+ description: Internal failure. Details are not exposed; report `request_id` to support.
427
+ content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
428
+ Unavailable:
429
+ description: No usable deployment or effective customer price exists for the selected model (`50301`).
430
+ content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
431
+ schemas:
432
+ EnvelopeBase:
433
+ type: object
434
+ required: [code, msg, request_id]
435
+ properties:
436
+ code: { type: integer }
437
+ msg: { type: string }
438
+ request_id:
439
+ type: string
440
+ description: Correlation identifier. Include it in support requests.
441
+ ErrorEnvelope:
442
+ allOf:
443
+ - $ref: '#/components/schemas/EnvelopeBase'
444
+ - type: object
445
+ properties:
446
+ code:
447
+ type: integer
448
+ enum: [400, 401, 40201, 40202, 403, 40301, 40302, 40303, 404, 409, 413, 429, 500, 50301]
449
+ msg: { type: string }
450
+ CreateTaskRequest:
451
+ type: object
452
+ required: [model, input]
453
+ additionalProperties: false
454
+ properties:
455
+ model:
456
+ type: string
457
+ minLength: 1
458
+ description: Exact `model` value from the model catalog.
459
+ input:
460
+ type: object
461
+ description: Validated against the selected model's `inputSchema`.
462
+ additionalProperties: true
463
+ callBackUrl:
464
+ type: string
465
+ format: uri
466
+ description: Optional public HTTP(S) endpoint for terminal task delivery.
467
+ mature:
468
+ type: boolean
469
+ default: false
470
+ description: Optional execution metadata passed through to the selected model. The platform applies no account, API-key, model-capability, channel-capability, or content-preflight gate to this field.
471
+ CreateTaskResponse:
472
+ type: object
473
+ required: [taskId, state, estimatedCost]
474
+ properties:
475
+ taskId: { type: string }
476
+ state:
477
+ type: string
478
+ enum: [queued, running, succeeded, failed, canceled, expired]
479
+ description: "`queued` for a new submission; an idempotent replay reports the original task's current state."
480
+ estimatedCost:
481
+ allOf:
482
+ - $ref: '#/components/schemas/USDString'
483
+ description: Funds held when the task is accepted and the maximum amount charged for that task. Unused funds are released; provider overage is absorbed by the platform.
484
+ TaskActionRequest:
485
+ type: object
486
+ required: [taskId]
487
+ additionalProperties: false
488
+ properties:
489
+ taskId: { type: string, minLength: 1 }
490
+ RetryTaskResponse:
491
+ allOf:
492
+ - $ref: '#/components/schemas/CreateTaskResponse'
493
+ - type: object
494
+ required: [sourceTaskId]
495
+ properties:
496
+ sourceTaskId: { type: string }
497
+ RetryTaskEnvelope:
498
+ allOf:
499
+ - $ref: '#/components/schemas/EnvelopeBase'
500
+ - type: object
501
+ required: [data]
502
+ properties:
503
+ code: { type: integer, const: 200 }
504
+ msg: { type: string, const: success }
505
+ data: { $ref: '#/components/schemas/RetryTaskResponse' }
506
+ CreateTaskEnvelope:
507
+ allOf:
508
+ - $ref: '#/components/schemas/EnvelopeBase'
509
+ - type: object
510
+ required: [data]
511
+ properties:
512
+ code: { type: integer, const: 200 }
513
+ msg: { type: string, const: success }
514
+ data: { $ref: '#/components/schemas/CreateTaskResponse' }
515
+ TaskRecord:
516
+ type: object
517
+ required: [taskId, model, state, cost, settled, createdAt]
518
+ properties:
519
+ taskId: { type: string }
520
+ sourceTaskId: { type: string, description: Present when this task was created by explicit retry. }
521
+ model: { type: string }
522
+ state:
523
+ type: string
524
+ enum: [queued, running, succeeded, failed, canceled, expired]
525
+ input:
526
+ description: Normalized model input. Omitted after retention redaction.
527
+ type: object
528
+ additionalProperties: true
529
+ output:
530
+ description: Normalized model output when available. Omitted after retention redaction.
531
+ type: object
532
+ additionalProperties: true
533
+ errorCode: { type: string }
534
+ errorMessage: { type: string }
535
+ cost: { $ref: '#/components/schemas/USDString' }
536
+ settled:
537
+ type: boolean
538
+ description: When false, `cost` is the held estimate. On success the final charge is capped at that hold; unused funds are released. Failed or expired tasks release the hold in full.
539
+ createdAt: { type: string, format: date-time }
540
+ completedAt: { type: string, format: date-time }
541
+ TaskRecordEnvelope:
542
+ allOf:
543
+ - $ref: '#/components/schemas/EnvelopeBase'
544
+ - type: object
545
+ required: [data]
546
+ properties:
547
+ code: { type: integer, const: 200 }
548
+ msg: { type: string, const: success }
549
+ data: { $ref: '#/components/schemas/TaskRecord' }
550
+ LegacyWebhookPayload:
551
+ type: object
552
+ deprecated: true
553
+ description: Version 1 compatibility callback. New integrations must use version 2.
554
+ required: [task_id, model, state, cost, created_at]
555
+ properties:
556
+ task_id: { type: string }
557
+ model: { type: string }
558
+ state: { type: string, enum: [queued, running, succeeded, failed, canceled, expired] }
559
+ output: { type: object, additionalProperties: true }
560
+ error_code: { type: string }
561
+ error_message: { type: string }
562
+ cost: { $ref: '#/components/schemas/USDString' }
563
+ created_at: { type: string, format: date-time }
564
+ Balance:
565
+ type: object
566
+ required: [available, held, total]
567
+ properties:
568
+ available: { $ref: '#/components/schemas/USDString' }
569
+ held: { $ref: '#/components/schemas/USDString' }
570
+ total: { $ref: '#/components/schemas/USDString' }
571
+ BalanceEnvelope:
572
+ allOf:
573
+ - $ref: '#/components/schemas/EnvelopeBase'
574
+ - type: object
575
+ required: [data]
576
+ properties:
577
+ code: { type: integer, const: 200 }
578
+ msg: { type: string, const: success }
579
+ data: { $ref: '#/components/schemas/Balance' }
580
+ APIModelPrice:
581
+ type: object
582
+ required: [variant, unit, price, currency]
583
+ properties:
584
+ variant:
585
+ type: string
586
+ description: Value used by `input.resolution`; empty when the model has one price tier.
587
+ unit: { type: string, enum: [per_image, per_second, per_request, per_1k_tokens] }
588
+ price: { $ref: '#/components/schemas/USDString' }
589
+ currency: { type: string, const: USD }
590
+ APIModel:
591
+ type: object
592
+ required: [model, family, displayName, provider, modality, tasks, async, mature, policyTier, taskTimeoutSeconds, enabled, available, quantityField, pricing, version, availability, badges, relatedModels, updatedAt]
593
+ properties:
594
+ model: { type: string }
595
+ family:
596
+ type: string
597
+ description: Stable product-family identifier; endpoint variants are listed in `tasks` and expressed by `inputSchema`.
598
+ displayName: { type: string }
599
+ provider: { type: string }
600
+ modality: { type: string, enum: [image, video, audio, text] }
601
+ tasks: { type: array, items: { type: string } }
602
+ async: { type: boolean }
603
+ mature:
604
+ type: boolean
605
+ description: Informational model-creator capability metadata only. It does not participate in routing, authorization, or request rejection.
606
+ policyTier:
607
+ type: string
608
+ enum: [unrestricted, borderline, unspecified]
609
+ description: Informational model-creator policy metadata only. It does not participate in routing, authorization, or request rejection.
610
+ taskTimeoutSeconds:
611
+ type: integer
612
+ minimum: 1
613
+ description: Absolute platform execution deadline before timeout/refund; not media output duration.
614
+ maxOutputDurationSeconds:
615
+ type: integer
616
+ minimum: 1
617
+ description: Maximum `input.duration` accepted by the model schema; omitted when the model has no declared duration maximum.
618
+ enabled: { type: boolean }
619
+ available: { type: boolean }
620
+ quantityField: { type: string }
621
+ pricing: { type: array, items: { $ref: '#/components/schemas/APIModelPrice' } }
622
+ startingPrice: { $ref: '#/components/schemas/APIModelPrice' }
623
+ inputSchema:
624
+ type: object
625
+ description: JSON Schema draft 2020-12 used by `createTask.input` validation.
626
+ additionalProperties: true
627
+ version:
628
+ type: string
629
+ description: Provider/model version label used to detect catalog changes.
630
+ availability:
631
+ type: string
632
+ enum: [planned, available, preview, maintenance]
633
+ badges:
634
+ type: array
635
+ description: Closed capability and licensing vocabulary; clients should ignore unknown future IDs.
636
+ items:
637
+ type: string
638
+ enum: [commercial_use, no_watermark, commercial_restricted, reference_input, audio_output, streaming, fast_tier, high_resolution, long_context, mature_capable, policy_unrestricted, policy_borderline]
639
+ relatedModels:
640
+ type: array
641
+ description: Curated model identifiers suitable for alternatives or workflow steps.
642
+ items: { type: string }
643
+ examples:
644
+ type: array
645
+ description: Inputs validated against this model's current input schema.
646
+ items: { $ref: '#/components/schemas/APIModelExample' }
647
+ updatedAt: { type: string, format: date-time }
648
+ APIModelExample:
649
+ type: object
650
+ required: [id, input, sortWeight]
651
+ properties:
652
+ id: { type: string }
653
+ input: { type: object, additionalProperties: true }
654
+ sortWeight: { type: integer }
655
+ APIModelList:
656
+ type: object
657
+ required: [total, items]
658
+ properties:
659
+ total: { type: integer, minimum: 0 }
660
+ items: { type: array, items: { $ref: '#/components/schemas/APIModel' } }
661
+ ModelEnvelope:
662
+ allOf:
663
+ - $ref: '#/components/schemas/EnvelopeBase'
664
+ - type: object
665
+ required: [data]
666
+ properties:
667
+ code: { type: integer, const: 200 }
668
+ msg: { type: string, const: success }
669
+ data: { $ref: '#/components/schemas/APIModel' }
670
+ ModelListEnvelope:
671
+ allOf:
672
+ - $ref: '#/components/schemas/EnvelopeBase'
673
+ - type: object
674
+ required: [data]
675
+ properties:
676
+ code: { type: integer, const: 200 }
677
+ msg: { type: string, const: success }
678
+ data: { $ref: '#/components/schemas/APIModelList' }
679
+ UploadURLRequest:
680
+ type: object
681
+ required: [contentType, bytes]
682
+ additionalProperties: false
683
+ properties:
684
+ contentType: { type: string, enum: [image/jpeg, image/png, image/webp, image/gif] }
685
+ bytes: { type: integer, minimum: 1, maximum: 10485760 }
686
+ UploadURLResponse:
687
+ type: object
688
+ required: [fileId, key, uploadUrl, method, headers, expiresAt, maxBytes]
689
+ properties:
690
+ fileId: { type: string, pattern: '^fil_' }
691
+ key: { type: string, pattern: '^spicy://f/fil_', description: Compatibility alias for the final URI; unusable until commit succeeds. }
692
+ uploadUrl: { type: string, format: uri }
693
+ method: { type: string, const: PUT }
694
+ headers: { type: object, additionalProperties: { type: string } }
695
+ expiresAt: { type: string, format: date-time }
696
+ maxBytes: { type: integer, minimum: 1 }
697
+ UploadURLEnvelope:
698
+ allOf:
699
+ - $ref: '#/components/schemas/EnvelopeBase'
700
+ - type: object
701
+ required: [data]
702
+ properties:
703
+ code: { type: integer, const: 200 }
704
+ msg: { type: string, const: success }
705
+ data: { $ref: '#/components/schemas/UploadURLResponse' }
706
+ FileCommitResponse:
707
+ type: object
708
+ required: [fileId, status, bytes, contentType, sha256, uri, expiresAt]
709
+ properties:
710
+ fileId: { type: string, pattern: '^fil_' }
711
+ status: { type: string, const: ready }
712
+ bytes: { type: integer, minimum: 1 }
713
+ contentType: { type: string, enum: [image/jpeg, image/png, image/webp, image/gif] }
714
+ sha256: { type: string, pattern: '^[a-f0-9]{64}$' }
715
+ uri: { type: string, pattern: '^spicy://f/fil_' }
716
+ expiresAt: { type: string, format: date-time }
717
+ FileCommitEnvelope:
718
+ allOf:
719
+ - $ref: '#/components/schemas/EnvelopeBase'
720
+ - type: object
721
+ required: [data]
722
+ properties:
723
+ code: { type: integer, const: 200 }
724
+ msg: { type: string, const: success }
725
+ data: { $ref: '#/components/schemas/FileCommitResponse' }
726
+ DownloadURLRequest:
727
+ type: object
728
+ required: [taskId]
729
+ additionalProperties: false
730
+ properties:
731
+ taskId: { type: string, minLength: 1 }
732
+ key: { type: string, description: Optional output key; empty selects the first output. }
733
+ DownloadURLResponse:
734
+ type: object
735
+ required: [key, url, expiresAt]
736
+ properties:
737
+ key: { type: string }
738
+ url: { type: string, format: uri }
739
+ expiresAt: { type: string, format: date-time }
740
+ DownloadURLEnvelope:
741
+ allOf:
742
+ - $ref: '#/components/schemas/EnvelopeBase'
743
+ - type: object
744
+ required: [data]
745
+ properties:
746
+ code: { type: integer, const: 200 }
747
+ msg: { type: string, const: success }
748
+ data: { $ref: '#/components/schemas/DownloadURLResponse' }
749
+ USDString:
750
+ type: string
751
+ pattern: '^-?[0-9]+(?:\.[0-9]+)?$'
752
+ description: Decimal USD amount encoded as a string to avoid binary floating-point loss.