@spicyapi/sdk 0.1.0 → 0.2.1

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.
@@ -9,6 +9,10 @@ info:
9
9
  `{code,msg,data,request_id}` envelope. A successful HTTP response has
10
10
  `code: 200`; asynchronous task success or failure is expressed by
11
11
  `data.state`, not by the envelope code.
12
+
13
+ The `/v1` operations tagged `OpenAI Compatible` are the one documented
14
+ exception: they return OpenAI / Anthropic response and error objects so
15
+ the official SDKs work by changing only `base_url`.
12
16
  servers:
13
17
  - url: https://api.spicyapi.ai
14
18
  description: Production
@@ -17,9 +21,46 @@ tags:
17
21
  - name: Models
18
22
  - name: Account
19
23
  - name: Media
24
+ - name: OpenAI Compatible
25
+ description: OpenAI / Anthropic protocol shells under `/v1`. They share authentication, limits, validation and billing with `/api/v1` but use the external protocol's own response and error shapes.
20
26
  security:
21
27
  - bearerAuth: []
22
28
  paths:
29
+ /api/v1/jobs/quote:
30
+ post:
31
+ tags: [Tasks]
32
+ operationId: quoteTask
33
+ summary: Quote a task before reserving funds
34
+ description: |
35
+ Validates the same model input and admission rules as task creation,
36
+ without creating a task, reserving funds or starting generation.
37
+ The signed quote is bound to this account, API key and request and is
38
+ valid for five minutes. Pass quoteId and expectedCost with the same
39
+ request to createTask or jobs/stream. New acceptance rejects an expired
40
+ or changed quote with business code 40901. A replay of an already
41
+ accepted Idempotency-Key still returns the original task.
42
+ A quote does not reserve capacity or guarantee future availability.
43
+ requestBody:
44
+ required: true
45
+ content:
46
+ application/json:
47
+ schema: { $ref: '#/components/schemas/CreateTaskRequest' }
48
+ responses:
49
+ '200':
50
+ description: Customer price and maximum charge; no funds were reserved.
51
+ headers:
52
+ Cache-Control:
53
+ schema: { type: string, const: no-store }
54
+ content:
55
+ application/json:
56
+ schema: { $ref: '#/components/schemas/TaskQuoteEnvelope' }
57
+ '400': { $ref: '#/components/responses/BadRequest' }
58
+ '401': { $ref: '#/components/responses/Unauthorized' }
59
+ '402': { $ref: '#/components/responses/PaymentRequired' }
60
+ '403': { $ref: '#/components/responses/Forbidden' }
61
+ '409': { $ref: '#/components/responses/Conflict' }
62
+ '429': { $ref: '#/components/responses/RateLimited' }
63
+ '503': { $ref: '#/components/responses/Unavailable' }
23
64
  /api/v1/jobs/createTask:
24
65
  post:
25
66
  tags: [Tasks]
@@ -32,7 +73,7 @@ paths:
32
73
 
33
74
  An `Idempotency-Key` is optional but strongly recommended. For the same
34
75
  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
76
+ model, input, and callback URL within 24 hours returns the
36
77
  original task and does not reserve funds twice. A sibling API key cannot
37
78
  replay the mapping: it receives `409 Conflict` without the original task
38
79
  ID. Reusing the key with different request semantics also returns
@@ -150,6 +191,50 @@ paths:
150
191
  schema: { $ref: '#/components/schemas/ErrorEnvelope' }
151
192
  '429': { $ref: '#/components/responses/RateLimited' }
152
193
  '500': { $ref: '#/components/responses/ServerError' }
194
+ /api/v1/jobs/stream:
195
+ post:
196
+ tags: [Tasks]
197
+ operationId: streamTask
198
+ summary: Run a text model and stream the answer
199
+ description: |
200
+ Text models only. Applies the same validation, quota, pricing and hold as
201
+ `createTask`, then keeps the connection open and returns the model answer
202
+ as a Server-Sent Events stream. Each event is a `data:` line carrying one
203
+ JSON chunk; the stream ends with `data: [DONE]`.
204
+
205
+ The hold is settled when the stream completes. If the upstream call fails
206
+ before the first event, the response is a normal JSON error and the hold is
207
+ released. If it fails mid-stream the connection is terminated with an
208
+ `error` event and the hold is still released — a truncated answer is never
209
+ charged as a completed one.
210
+
211
+ Generation models (image, video, audio) are asynchronous and must use
212
+ `createTask` with `recordInfo` polling; calling this endpoint for them
213
+ returns `503`.
214
+ parameters:
215
+ - $ref: '#/components/parameters/IdempotencyKey'
216
+ requestBody:
217
+ required: true
218
+ content:
219
+ application/json:
220
+ schema:
221
+ $ref: '#/components/schemas/CreateTaskRequest'
222
+ responses:
223
+ '200':
224
+ description: Server-Sent Events stream of the model answer.
225
+ content:
226
+ text/event-stream:
227
+ schema: { type: string }
228
+ '400': { $ref: '#/components/responses/BadRequest' }
229
+ '401': { $ref: '#/components/responses/Unauthorized' }
230
+ '402': { $ref: '#/components/responses/PaymentRequired' }
231
+ '403': { $ref: '#/components/responses/Forbidden' }
232
+ '404': { $ref: '#/components/responses/NotFound' }
233
+ '409': { $ref: '#/components/responses/Conflict' }
234
+ '429': { $ref: '#/components/responses/RateLimited' }
235
+ '500': { $ref: '#/components/responses/ServerError' }
236
+ '503': { $ref: '#/components/responses/Unavailable' }
237
+
153
238
  /api/v1/jobs/retry:
154
239
  post:
155
240
  tags: [Tasks]
@@ -200,6 +285,7 @@ paths:
200
285
  - name: provider
201
286
  in: query
202
287
  schema: { type: string }
288
+ description: Exact model creator/author identifier. This never identifies an execution channel.
203
289
  - name: task
204
290
  in: query
205
291
  schema: { type: string }
@@ -244,7 +330,7 @@ paths:
244
330
  description: |
245
331
  `model` is the exact value used by `createTask.model` and may contain
246
332
  slashes. URL-encode embedded slashes when using clients that treat path
247
- parameters as one segment (for example `bytedance%2Fseedance-2-fast`).
333
+ parameters as one segment (for example `family%2Fversion%2Ftask`).
248
334
  parameters:
249
335
  - name: model
250
336
  in: path
@@ -291,7 +377,9 @@ paths:
291
377
  description: |
292
378
  The server generates the object key. Upload the exact declared number of
293
379
  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,
380
+ JPEG, PNG, WebP and GIF images (10 MiB), or MP4/WebM video and
381
+ MP3/WAV audio (100 MiB, at most 600 seconds). Audio/video commit verifies
382
+ format and measurable duration; it does not inspect adult content. After PUT,
295
383
  call the file commit endpoint and use its spicy:// URI in task input.
296
384
  requestBody:
297
385
  required: true
@@ -320,8 +408,10 @@ paths:
320
408
  description: |
321
409
  Compares the stored byte count and media type with the ticket, checks
322
410
  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.
411
+ to an immutable private key. Reference this uploaded file with the returned
412
+ spicy:// URI. Publicly accessible HTTPS media URLs can instead be passed
413
+ directly in supported model input fields, without uploading. Repeating
414
+ a successful commit is idempotent.
325
415
  parameters:
326
416
  - name: fileId
327
417
  in: path
@@ -369,6 +459,272 @@ paths:
369
459
  '413': { $ref: '#/components/responses/PayloadTooLarge' }
370
460
  '429': { $ref: '#/components/responses/RateLimited' }
371
461
  '500': { $ref: '#/components/responses/ServerError' }
462
+ # ── OpenAI / Anthropic compatible surface ──────────────────────────────
463
+ # These operations use the response shapes of the external protocols they
464
+ # imitate (OpenAI error objects, Anthropic error objects, SSE event streams)
465
+ # instead of the `{code,msg,data,request_id}` envelope. Authentication, rate
466
+ # limits, validation, pricing and holds are shared with `/api/v1`.
467
+ /v1/models:
468
+ get:
469
+ tags: [OpenAI Compatible]
470
+ operationId: listOpenAIModels
471
+ summary: List callable models in the OpenAI `model` list shape
472
+ description: |
473
+ Returns every model that can be called right now. `id` is the SpicyAPI
474
+ model identifier accepted by every `/v1` and `/api/v1` operation;
475
+ `owned_by` is the model publisher (for example a research lab), never
476
+ an execution supplier. Pricing is not included; use `/api/v1/models`.
477
+ responses:
478
+ '200':
479
+ description: OpenAI-style model list.
480
+ content:
481
+ application/json:
482
+ schema:
483
+ $ref: '#/components/schemas/OpenAIModelList'
484
+ '401': { $ref: '#/components/responses/OpenAIError' }
485
+ '403': { $ref: '#/components/responses/OpenAIError' }
486
+ '429': { $ref: '#/components/responses/OpenAIError' }
487
+ '500': { $ref: '#/components/responses/OpenAIError' }
488
+ '503': { $ref: '#/components/responses/OpenAIError' }
489
+
490
+ /v1/chat/completions:
491
+ post:
492
+ tags: [OpenAI Compatible]
493
+ operationId: createChatCompletion
494
+ summary: OpenAI Chat Completions on a SpicyAPI text model
495
+ description: |
496
+ Accepts an OpenAI Chat Completions request. `messages`, `tools`,
497
+ `tool_choice`, `response_format`, `max_tokens` (or
498
+ `max_completion_tokens`), `temperature` and `top_p` map onto the
499
+ model's public input fields of the same name; `stream`,
500
+ `stream_options`, `user`, `metadata` and `store` are handled by this
501
+ layer. Any other parameter is forwarded to the model's input schema
502
+ and rejected with `400` when the model does not declare it — a
503
+ parameter is never dropped silently.
504
+
505
+ `stream: true` returns a Server-Sent Events stream of
506
+ `chat.completion.chunk` objects terminated by `data: [DONE]`.
507
+ `stream: false` returns one `chat.completion` object. The same hold,
508
+ settlement and refund rules as `/api/v1/jobs/stream` apply.
509
+ parameters:
510
+ - $ref: '#/components/parameters/IdempotencyKey'
511
+ requestBody:
512
+ required: true
513
+ content:
514
+ application/json:
515
+ schema:
516
+ $ref: '#/components/schemas/ChatCompletionRequest'
517
+ responses:
518
+ '200':
519
+ description: One `chat.completion` object, or an SSE stream when `stream` is true.
520
+ content:
521
+ application/json:
522
+ schema:
523
+ $ref: '#/components/schemas/ChatCompletion'
524
+ text/event-stream:
525
+ schema: { type: string }
526
+ '400': { $ref: '#/components/responses/OpenAIError' }
527
+ '401': { $ref: '#/components/responses/OpenAIError' }
528
+ '402': { $ref: '#/components/responses/OpenAIError' }
529
+ '403': { $ref: '#/components/responses/OpenAIError' }
530
+ '404': { $ref: '#/components/responses/OpenAIError' }
531
+ '409': { $ref: '#/components/responses/OpenAIError' }
532
+ '413': { $ref: '#/components/responses/OpenAIError' }
533
+ '429': { $ref: '#/components/responses/OpenAIError' }
534
+ '500': { $ref: '#/components/responses/OpenAIError' }
535
+ '503': { $ref: '#/components/responses/OpenAIError' }
536
+
537
+ /v1/responses:
538
+ post:
539
+ tags: [OpenAI Compatible]
540
+ operationId: createResponse
541
+ summary: OpenAI Responses API subset on a SpicyAPI text model
542
+ description: |
543
+ Accepts the stateless subset of the OpenAI Responses API: `model`,
544
+ `input` (string or item array with `message`, `function_call` and
545
+ `function_call_output` items), `instructions`, `max_output_tokens`,
546
+ `temperature`, `top_p`, `tools` (function tools), `tool_choice`,
547
+ `text.format`, `reasoning.effort` and `stream`. The request is
548
+ translated to a chat completion internally. `previous_response_id`
549
+ is rejected with `400`; send the full history in `input` instead.
550
+
551
+ Non-streaming calls return a `response` object with `output[]`
552
+ (`message` and `function_call` items) and `usage`. Streaming calls
553
+ emit `response.created`, `response.output_text.delta` and
554
+ `response.completed` events.
555
+ parameters:
556
+ - $ref: '#/components/parameters/IdempotencyKey'
557
+ requestBody:
558
+ required: true
559
+ content:
560
+ application/json:
561
+ schema:
562
+ $ref: '#/components/schemas/ResponsesRequest'
563
+ responses:
564
+ '200':
565
+ description: One `response` object, or an SSE stream when `stream` is true.
566
+ content:
567
+ application/json:
568
+ schema:
569
+ $ref: '#/components/schemas/ResponseObject'
570
+ text/event-stream:
571
+ schema: { type: string }
572
+ '400': { $ref: '#/components/responses/OpenAIError' }
573
+ '401': { $ref: '#/components/responses/OpenAIError' }
574
+ '402': { $ref: '#/components/responses/OpenAIError' }
575
+ '403': { $ref: '#/components/responses/OpenAIError' }
576
+ '404': { $ref: '#/components/responses/OpenAIError' }
577
+ '409': { $ref: '#/components/responses/OpenAIError' }
578
+ '413': { $ref: '#/components/responses/OpenAIError' }
579
+ '429': { $ref: '#/components/responses/OpenAIError' }
580
+ '500': { $ref: '#/components/responses/OpenAIError' }
581
+ '503': { $ref: '#/components/responses/OpenAIError' }
582
+
583
+ /v1/messages:
584
+ post:
585
+ tags: [OpenAI Compatible]
586
+ operationId: createMessage
587
+ summary: Anthropic Messages API on a SpicyAPI text model
588
+ description: |
589
+ Accepts an Anthropic Messages request (`model`, `system`, `messages`
590
+ with text / image / tool_use / tool_result blocks, `max_tokens`,
591
+ `temperature`, `top_p`, `stop_sequences`, `tools`, `tool_choice`,
592
+ `stream`) and translates it to a chat completion internally.
593
+ Non-streaming calls return an Anthropic `message` object. Streaming
594
+ calls emit `message_start`, `content_block_start`,
595
+ `content_block_delta`, `content_block_stop`, `message_delta` and
596
+ `message_stop` events. Errors use the Anthropic
597
+ `{type:"error",error:{type,message}}` shape.
598
+ parameters:
599
+ - $ref: '#/components/parameters/IdempotencyKey'
600
+ requestBody:
601
+ required: true
602
+ content:
603
+ application/json:
604
+ schema:
605
+ $ref: '#/components/schemas/AnthropicMessageRequest'
606
+ responses:
607
+ '200':
608
+ description: One Anthropic `message` object, or an SSE stream when `stream` is true.
609
+ content:
610
+ application/json:
611
+ schema:
612
+ $ref: '#/components/schemas/AnthropicMessage'
613
+ text/event-stream:
614
+ schema: { type: string }
615
+ '400': { $ref: '#/components/responses/AnthropicError' }
616
+ '401': { $ref: '#/components/responses/AnthropicError' }
617
+ '402': { $ref: '#/components/responses/AnthropicError' }
618
+ '403': { $ref: '#/components/responses/AnthropicError' }
619
+ '404': { $ref: '#/components/responses/AnthropicError' }
620
+ '409': { $ref: '#/components/responses/AnthropicError' }
621
+ '413': { $ref: '#/components/responses/AnthropicError' }
622
+ '429': { $ref: '#/components/responses/AnthropicError' }
623
+ '500': { $ref: '#/components/responses/AnthropicError' }
624
+ '503': { $ref: '#/components/responses/AnthropicError' }
625
+
626
+ /v1/videos:
627
+ post:
628
+ tags: [OpenAI Compatible]
629
+ operationId: createVideo
630
+ summary: Create a video generation task in the OpenAI Videos shape
631
+ description: |
632
+ Projection of `createTask` onto the OpenAI Videos API. `model` must be
633
+ a video endpoint identifier from `/v1/models`. `prompt` maps to the
634
+ model's `prompt`; `input_reference` (an HTTPS URL or a committed
635
+ `spicy://` upload) maps to `image_url` / `image_urls`; `seconds` maps to
636
+ `duration_seconds`; `size` (`WIDTHxHEIGHT`) maps to `resolution` and/or
637
+ `aspect_ratio`, or to `width` / `height` when the model declares pixel
638
+ dimensions. A parameter the selected model does not declare is
639
+ rejected with `400`. The same hold and idempotency rules as
640
+ `createTask` apply; the returned object never contains prices.
641
+ parameters:
642
+ - $ref: '#/components/parameters/IdempotencyKey'
643
+ requestBody:
644
+ required: true
645
+ content:
646
+ application/json:
647
+ schema:
648
+ $ref: '#/components/schemas/VideoCreateRequest'
649
+ responses:
650
+ '200':
651
+ description: The accepted task as a `video` object in `queued` state (an idempotent replay reports the current state).
652
+ content:
653
+ application/json:
654
+ schema:
655
+ $ref: '#/components/schemas/Video'
656
+ '400': { $ref: '#/components/responses/OpenAIError' }
657
+ '401': { $ref: '#/components/responses/OpenAIError' }
658
+ '402': { $ref: '#/components/responses/OpenAIError' }
659
+ '403': { $ref: '#/components/responses/OpenAIError' }
660
+ '404': { $ref: '#/components/responses/OpenAIError' }
661
+ '409': { $ref: '#/components/responses/OpenAIError' }
662
+ '413': { $ref: '#/components/responses/OpenAIError' }
663
+ '429': { $ref: '#/components/responses/OpenAIError' }
664
+ '500': { $ref: '#/components/responses/OpenAIError' }
665
+ '503': { $ref: '#/components/responses/OpenAIError' }
666
+
667
+ /v1/videos/{videoId}:
668
+ get:
669
+ tags: [OpenAI Compatible]
670
+ operationId: retrieveVideo
671
+ summary: Retrieve a video task as an OpenAI `video` object
672
+ description: |
673
+ Task states map to `queued` → `queued`, `running` → `in_progress`,
674
+ `succeeded` → `completed`, and `failed` / `canceled` / `expired` →
675
+ `failed` with an `error` object. Only the account and API key that
676
+ created the task can read it; other identifiers return `404`.
677
+ parameters:
678
+ - name: videoId
679
+ in: path
680
+ required: true
681
+ schema: { type: string }
682
+ responses:
683
+ '200':
684
+ description: Current video task state.
685
+ content:
686
+ application/json:
687
+ schema:
688
+ $ref: '#/components/schemas/Video'
689
+ '401': { $ref: '#/components/responses/OpenAIError' }
690
+ '403': { $ref: '#/components/responses/OpenAIError' }
691
+ '404': { $ref: '#/components/responses/OpenAIError' }
692
+ '429': { $ref: '#/components/responses/OpenAIError' }
693
+ '500': { $ref: '#/components/responses/OpenAIError' }
694
+ '503': { $ref: '#/components/responses/OpenAIError' }
695
+
696
+ /v1/videos/{videoId}/content:
697
+ get:
698
+ tags: [OpenAI Compatible]
699
+ operationId: downloadVideoContent
700
+ summary: Redirect to the generated video file
701
+ description: |
702
+ Responds `302 Found` with a short-lived signed download URL for the
703
+ task's primary output, the same address `/api/v1/common/download-url`
704
+ would issue. `409` while the task is still queued or running; `404`
705
+ when the task failed or has no downloadable output.
706
+ parameters:
707
+ - name: videoId
708
+ in: path
709
+ required: true
710
+ schema: { type: string }
711
+ responses:
712
+ '302':
713
+ description: Redirect to the signed media URL. The redirect must not be cached.
714
+ headers:
715
+ Location:
716
+ description: Short-lived signed download URL.
717
+ schema: { type: string, format: uri }
718
+ Cache-Control:
719
+ schema: { type: string, const: no-store }
720
+ '401': { $ref: '#/components/responses/OpenAIError' }
721
+ '403': { $ref: '#/components/responses/OpenAIError' }
722
+ '404': { $ref: '#/components/responses/OpenAIError' }
723
+ '409': { $ref: '#/components/responses/OpenAIError' }
724
+ '429': { $ref: '#/components/responses/OpenAIError' }
725
+ '500': { $ref: '#/components/responses/OpenAIError' }
726
+ '503': { $ref: '#/components/responses/OpenAIError' }
727
+
372
728
  components:
373
729
  securitySchemes:
374
730
  bearerAuth:
@@ -395,7 +751,10 @@ components:
395
751
  schema: { type: integer, minimum: 1 }
396
752
  responses:
397
753
  BadRequest:
398
- description: Invalid JSON, query, callback URL, model input, or media declaration.
754
+ description: >-
755
+ Invalid JSON, query, callback URL, model input, or media declaration.
756
+ Invalid callBackUrl values return code 400 and msg "Invalid callback URL"
757
+ with request_id. Submitted hosts, IP addresses, and DNS diagnostics are not echoed.
399
758
  content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
400
759
  Unauthorized:
401
760
  description: Missing, malformed, expired, revoked, or otherwise invalid API key.
@@ -428,6 +787,12 @@ components:
428
787
  Unavailable:
429
788
  description: No usable deployment or effective customer price exists for the selected model (`50301`).
430
789
  content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
790
+ OpenAIError:
791
+ description: Error in the OpenAI `{error:{message,type,param,code}}` shape. The HTTP status is the same one `/api/v1` would return.
792
+ content: { application/json: { schema: { $ref: '#/components/schemas/OpenAIErrorBody' } } }
793
+ AnthropicError:
794
+ description: Error in the Anthropic `{type:"error",error:{type,message}}` shape.
795
+ content: { application/json: { schema: { $ref: '#/components/schemas/AnthropicErrorBody' } } }
431
796
  schemas:
432
797
  EnvelopeBase:
433
798
  type: object
@@ -445,7 +810,7 @@ components:
445
810
  properties:
446
811
  code:
447
812
  type: integer
448
- enum: [400, 401, 40201, 40202, 403, 40301, 40302, 40303, 404, 409, 413, 429, 500, 50301]
813
+ enum: [400, 401, 40201, 40202, 403, 40301, 40302, 40303, 404, 409, 40901, 413, 429, 500, 50301]
449
814
  msg: { type: string }
450
815
  CreateTaskRequest:
451
816
  type: object
@@ -463,11 +828,39 @@ components:
463
828
  callBackUrl:
464
829
  type: string
465
830
  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.
831
+ description: >-
832
+ Optional public HTTP(S) endpoint for terminal task delivery; HTTPS is recommended.
833
+ Only ports 80 and 443 are allowed, and URL credentials are rejected.
834
+ The hostname must resolve to public IP addresses and is checked again when connecting.
835
+ Invalid values return HTTP 400 with code 400, msg "Invalid callback URL", and request_id.
836
+ quoteId:
837
+ type: string
838
+ maxLength: 4096
839
+ description: Signed quote returned by jobs/quote for this same account, API key and request. Does not replace Idempotency-Key.
840
+ expectedCost:
841
+ allOf:
842
+ - $ref: '#/components/schemas/USDString'
843
+ description: Confirmed amount to reserve, with up to nine decimal places. A changed price returns business code 40901 before reservation.
844
+ TaskQuoteResponse:
845
+ type: object
846
+ required: [quoteId, model, estimatedCost, maxCharge, currency, quantity, unit, expiresAt]
847
+ additionalProperties: false
848
+ properties:
849
+ quoteId: { type: string }
850
+ model: { type: string, description: Resolved endpoint model identifier. }
851
+ estimatedCost: { $ref: '#/components/schemas/USDString' }
852
+ maxCharge: { $ref: '#/components/schemas/USDString' }
853
+ currency: { type: string, const: USD }
854
+ quantity: { type: string, description: Estimated billable quantity. }
855
+ unit: { type: string }
856
+ expiresAt: { type: string, format: date-time }
857
+ TaskQuoteEnvelope:
858
+ allOf:
859
+ - $ref: '#/components/schemas/EnvelopeBase'
860
+ - type: object
861
+ required: [data]
862
+ properties:
863
+ data: { $ref: '#/components/schemas/TaskQuoteResponse' }
471
864
  CreateTaskResponse:
472
865
  type: object
473
866
  required: [taskId, state, estimatedCost]
@@ -480,7 +873,7 @@ components:
480
873
  estimatedCost:
481
874
  allOf:
482
875
  - $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.
876
+ description: Funds held when the task is accepted and the maximum customer charge for that task. Unused funds are released and no later amount is collected above the hold.
484
877
  TaskActionRequest:
485
878
  type: object
486
879
  required: [taskId]
@@ -512,6 +905,31 @@ components:
512
905
  code: { type: integer, const: 200 }
513
906
  msg: { type: string, const: success }
514
907
  data: { $ref: '#/components/schemas/CreateTaskResponse' }
908
+ TaskOutput:
909
+ type: object
910
+ additionalProperties: true
911
+ description: First-party result metadata. Ready assets include directly usable temporary URLs; no download-ticket request is needed. Poll again to refresh an expired URL. Temporary URLs are bearer access grants; never attach your API key when fetching them.
912
+ properties:
913
+ text: { type: string }
914
+ assets:
915
+ type: array
916
+ items: { $ref: '#/components/schemas/TaskOutputAsset' }
917
+ TaskOutputAsset:
918
+ type: object
919
+ additionalProperties: true
920
+ properties:
921
+ key: { type: string, description: Compatibility identifier for optional download-url requests. }
922
+ url: { type: string, format: uri, description: First-party signed GET URL, normally valid for 20 minutes and never beyond the 14-day result retention period. Absent while pending or unavailable. }
923
+ expiresAt: { type: string, format: date-time, description: Expiry of this URL, not the result retention deadline. }
924
+ mime: { type: string }
925
+ width: { type: integer }
926
+ height: { type: integer }
927
+ durationSeconds: { type: number }
928
+ bytes: { type: integer, format: int64 }
929
+ role: { type: string }
930
+ nsfw: { type: boolean }
931
+ pending: { type: boolean }
932
+ unavailable: { type: boolean }
515
933
  TaskRecord:
516
934
  type: object
517
935
  required: [taskId, model, state, cost, settled, createdAt]
@@ -526,12 +944,9 @@ components:
526
944
  description: Normalized model input. Omitted after retention redaction.
527
945
  type: object
528
946
  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 }
947
+ output: { $ref: '#/components/schemas/TaskOutput' }
948
+ errorCode: { type: string, description: Stable SpicyAPI failure identifier; never a raw execution-channel code. }
949
+ errorMessage: { type: string, description: Safe normalized explanation without internal service names, hosts, task IDs, or raw errors. }
535
950
  cost: { $ref: '#/components/schemas/USDString' }
536
951
  settled:
537
952
  type: boolean
@@ -586,17 +1001,26 @@ components:
586
1001
  description: Value used by `input.resolution`; empty when the model has one price tier.
587
1002
  unit: { type: string, enum: [per_image, per_second, per_request, per_1k_tokens] }
588
1003
  price: { $ref: '#/components/schemas/USDString' }
1004
+ regularPrice: { $ref: '#/components/schemas/USDString' }
1005
+ offerLabel: { type: string }
1006
+ offerPercent: { type: string }
1007
+ offerEndsAt: { type: string, format: date-time }
589
1008
  currency: { type: string, const: USD }
590
1009
  APIModel:
591
1010
  type: object
592
1011
  required: [model, family, displayName, provider, modality, tasks, async, mature, policyTier, taskTimeoutSeconds, enabled, available, quantityField, pricing, version, availability, badges, relatedModels, updatedAt]
593
1012
  properties:
594
- model: { type: string }
1013
+ model:
1014
+ type: string
1015
+ description: Preferred callable identifier in publisher/model/task form. Image editing uses the edit suffix; previously published identifiers remain accepted.
1016
+ example: bytedance/seedream-5.0-lite/edit
595
1017
  family:
596
1018
  type: string
597
1019
  description: Stable product-family identifier; endpoint variants are listed in `tasks` and expressed by `inputSchema`.
598
1020
  displayName: { type: string }
599
- provider: { type: string }
1021
+ provider:
1022
+ type: string
1023
+ description: Model creator/author identifier. It never identifies an execution channel.
600
1024
  modality: { type: string, enum: [image, video, audio, text] }
601
1025
  tasks: { type: array, items: { type: string } }
602
1026
  async: { type: boolean }
@@ -614,7 +1038,10 @@ components:
614
1038
  maxOutputDurationSeconds:
615
1039
  type: integer
616
1040
  minimum: 1
617
- description: Maximum `input.duration` accepted by the model schema; omitted when the model has no declared duration maximum.
1041
+ description: >-
1042
+ Maximum output duration the model schema allows, taken from the `maximum` of its
1043
+ `duration_seconds` property; omitted when that property is absent or has no declared
1044
+ maximum. Distinct from `taskTimeoutSeconds`, which is the platform execution deadline.
618
1045
  enabled: { type: boolean }
619
1046
  available: { type: boolean }
620
1047
  quantityField: { type: string }
@@ -622,11 +1049,22 @@ components:
622
1049
  startingPrice: { $ref: '#/components/schemas/APIModelPrice' }
623
1050
  inputSchema:
624
1051
  type: object
625
- description: JSON Schema draft 2020-12 used by `createTask.input` validation.
1052
+ description: >-
1053
+ JSON Schema draft 2020-12 used by `createTask.input` validation and Playground rendering.
1054
+ Standard JSON Schema keywords validate. Presentation hints live only in a per-property `x-ui`
1055
+ object whose keys are snake_case and drawn from a closed set: `widget`, `order`, `label`,
1056
+ `placeholder`, `rows`, `step`, `unit`, `accept`, `max_size_mb`, `advanced`, `primary`,
1057
+ `affects_price`, `visible_when`, `enum_labels`. `x-ui.order` is the only source of field
1058
+ order (descending first). `x-ui.widget` is one of `textarea`, `text`, `upload`,
1059
+ `multi-upload`, `select`, `radio`, `slider`, `number`, `switch`, `json`, `object-list`,
1060
+ `chat-messages`, `hidden`. Root-level composition is limited to `anyOf`, where every branch
1061
+ contains only `required`, and `allOf`, where each branch is `if` (`required` or
1062
+ `properties.*.const`) plus `then` (`required`); `oneOf`, `not` and `dependentSchemas` never
1063
+ appear. Unknown hints must be ignored.
626
1064
  additionalProperties: true
627
1065
  version:
628
1066
  type: string
629
- description: Provider/model version label used to detect catalog changes.
1067
+ description: Public model-catalog version label used to detect schema or metadata changes.
630
1068
  availability:
631
1069
  type: string
632
1070
  enum: [planned, available, preview, maintenance]
@@ -681,8 +1119,8 @@ components:
681
1119
  required: [contentType, bytes]
682
1120
  additionalProperties: false
683
1121
  properties:
684
- contentType: { type: string, enum: [image/jpeg, image/png, image/webp, image/gif] }
685
- bytes: { type: integer, minimum: 1, maximum: 10485760 }
1122
+ contentType: { type: string, enum: [image/jpeg, image/png, image/webp, image/gif, video/mp4, video/webm, audio/mpeg, audio/wav] }
1123
+ bytes: { type: integer, minimum: 1, maximum: 104857600, description: Images are limited to 10 MiB; supported audio/video to 100 MiB. }
686
1124
  UploadURLResponse:
687
1125
  type: object
688
1126
  required: [fileId, key, uploadUrl, method, headers, expiresAt, maxBytes]
@@ -710,10 +1148,13 @@ components:
710
1148
  fileId: { type: string, pattern: '^fil_' }
711
1149
  status: { type: string, const: ready }
712
1150
  bytes: { type: integer, minimum: 1 }
713
- contentType: { type: string, enum: [image/jpeg, image/png, image/webp, image/gif] }
1151
+ contentType: { type: string, enum: [image/jpeg, image/png, image/webp, image/gif, video/mp4, video/webm, audio/mpeg, audio/wav] }
714
1152
  sha256: { type: string, pattern: '^[a-f0-9]{64}$' }
715
1153
  uri: { type: string, pattern: '^spicy://f/fil_' }
716
1154
  expiresAt: { type: string, format: date-time }
1155
+ durationSeconds: { type: string, description: Measured audio/video duration in seconds. }
1156
+ width: { type: integer, minimum: 0 }
1157
+ height: { type: integer, minimum: 0 }
717
1158
  FileCommitEnvelope:
718
1159
  allOf:
719
1160
  - $ref: '#/components/schemas/EnvelopeBase'
@@ -750,3 +1191,271 @@ components:
750
1191
  type: string
751
1192
  pattern: '^-?[0-9]+(?:\.[0-9]+)?$'
752
1193
  description: Decimal USD amount encoded as a string to avoid binary floating-point loss.
1194
+
1195
+ # ── OpenAI / Anthropic compatible shapes ───────────────────────────────
1196
+ OpenAIErrorBody:
1197
+ type: object
1198
+ required: [error]
1199
+ properties:
1200
+ error:
1201
+ type: object
1202
+ required: [message, type, code]
1203
+ properties:
1204
+ message: { type: string }
1205
+ type:
1206
+ type: string
1207
+ enum: [invalid_request_error, authentication_error, permission_error, insufficient_quota, rate_limit_error, server_error]
1208
+ param: { type: [string, 'null'] }
1209
+ code:
1210
+ type: string
1211
+ description: Stable machine-readable reason, for example `invalid_request`, `invalid_api_key`, `insufficient_balance`, `model_not_allowed`, `model_unavailable`, `rate_limit_exceeded`.
1212
+ AnthropicErrorBody:
1213
+ type: object
1214
+ required: [type, error]
1215
+ properties:
1216
+ type: { type: string, const: error }
1217
+ error:
1218
+ type: object
1219
+ required: [type, message]
1220
+ properties:
1221
+ type:
1222
+ type: string
1223
+ enum: [invalid_request_error, authentication_error, billing_error, permission_error, not_found_error, request_too_large, rate_limit_error, api_error, overloaded_error]
1224
+ message: { type: string }
1225
+ OpenAIModelList:
1226
+ type: object
1227
+ required: [object, data]
1228
+ properties:
1229
+ object: { type: string, const: list }
1230
+ data:
1231
+ type: array
1232
+ items:
1233
+ type: object
1234
+ required: [id, object, created, owned_by]
1235
+ properties:
1236
+ id: { type: string, description: SpicyAPI model identifier; may contain slashes. }
1237
+ object: { type: string, const: model }
1238
+ created: { type: integer, description: Unix seconds of the last catalog update. }
1239
+ owned_by: { type: string, description: Model publisher. }
1240
+ ChatCompletionRequest:
1241
+ type: object
1242
+ required: [model, messages]
1243
+ properties:
1244
+ model: { type: string }
1245
+ messages:
1246
+ type: array
1247
+ minItems: 1
1248
+ items:
1249
+ type: object
1250
+ required: [role]
1251
+ properties:
1252
+ role: { type: string }
1253
+ content:
1254
+ oneOf:
1255
+ - { type: [string, 'null'] }
1256
+ - { type: array, items: { type: object, additionalProperties: true } }
1257
+ tool_call_id: { type: string }
1258
+ tool_calls: { type: array, items: { type: object, additionalProperties: true } }
1259
+ reasoning_content:
1260
+ type: string
1261
+ description: Optional assistant reasoning text; availability and message roles follow the selected model input schema.
1262
+ additionalProperties: true
1263
+ stream: { type: boolean, default: false }
1264
+ max_tokens: { type: integer }
1265
+ max_completion_tokens: { type: integer, description: Alias of `max_tokens`. }
1266
+ temperature: { type: number }
1267
+ top_p: { type: number }
1268
+ tools: { type: array, items: { type: object, additionalProperties: true } }
1269
+ tool_choice: { oneOf: [{ type: string }, { type: object, additionalProperties: true }] }
1270
+ response_format: { type: object, additionalProperties: true }
1271
+ additionalProperties:
1272
+ description: Forwarded to the model input schema and rejected when undeclared.
1273
+ ChatCompletion:
1274
+ type: object
1275
+ required: [id, object, created, model, choices]
1276
+ properties:
1277
+ id: { type: string }
1278
+ object: { type: string, const: chat.completion }
1279
+ created: { type: integer }
1280
+ model: { type: string }
1281
+ choices:
1282
+ type: array
1283
+ items:
1284
+ type: object
1285
+ required: [index, message, finish_reason]
1286
+ properties:
1287
+ index: { type: integer }
1288
+ message:
1289
+ type: object
1290
+ properties:
1291
+ role: { type: string }
1292
+ content: { type: [string, 'null'] }
1293
+ refusal: { type: string }
1294
+ reasoning_content:
1295
+ type: string
1296
+ description: Optional reasoning text returned by models that expose it. Streaming chunks use the same field in delta.
1297
+ tool_calls: { type: array, items: { type: object, additionalProperties: true } }
1298
+ finish_reason: { type: string }
1299
+ logprobs: { type: 'null' }
1300
+ usage:
1301
+ type: object
1302
+ properties:
1303
+ prompt_tokens: { type: integer }
1304
+ completion_tokens: { type: integer }
1305
+ total_tokens: { type: integer }
1306
+ prompt_tokens_details:
1307
+ type: object
1308
+ properties:
1309
+ cached_tokens: { type: integer }
1310
+ cache_creation_input_tokens: { type: integer }
1311
+ cache_read_input_tokens: { type: integer }
1312
+ completion_tokens_details:
1313
+ type: object
1314
+ properties:
1315
+ reasoning_tokens:
1316
+ type: integer
1317
+ description: Included in completion_tokens; do not add it again when calculating usage.
1318
+ ResponsesRequest:
1319
+ type: object
1320
+ required: [model, input]
1321
+ properties:
1322
+ model: { type: string }
1323
+ input:
1324
+ oneOf:
1325
+ - { type: string }
1326
+ - { type: array, items: { type: object } }
1327
+ instructions: { type: string }
1328
+ max_output_tokens: { type: integer }
1329
+ temperature: { type: number }
1330
+ top_p: { type: number }
1331
+ tools: { type: array, items: { type: object } }
1332
+ tool_choice: { oneOf: [{ type: string }, { type: object }] }
1333
+ text: { type: object }
1334
+ reasoning: { type: object, properties: { effort: { type: string } } }
1335
+ stream: { type: boolean, default: false }
1336
+ ResponseObject:
1337
+ type: object
1338
+ required: [id, object, created_at, status, model, output]
1339
+ properties:
1340
+ id: { type: string }
1341
+ object: { type: string, const: response }
1342
+ created_at: { type: integer }
1343
+ status: { type: string, enum: [completed, incomplete, in_progress] }
1344
+ model: { type: string }
1345
+ output:
1346
+ type: array
1347
+ items:
1348
+ type: object
1349
+ required: [id, type, status]
1350
+ properties:
1351
+ id: { type: string }
1352
+ type: { type: string, enum: [message, function_call] }
1353
+ status: { type: string }
1354
+ role: { type: string }
1355
+ content:
1356
+ type: array
1357
+ items:
1358
+ type: object
1359
+ properties:
1360
+ type: { type: string, const: output_text }
1361
+ text: { type: string }
1362
+ annotations: { type: array, items: { type: object } }
1363
+ call_id: { type: string }
1364
+ name: { type: string }
1365
+ arguments: { type: string }
1366
+ error: { type: 'null' }
1367
+ incomplete_details:
1368
+ type: [object, 'null']
1369
+ properties:
1370
+ reason: { type: string }
1371
+ usage:
1372
+ type: object
1373
+ properties:
1374
+ input_tokens: { type: integer }
1375
+ output_tokens: { type: integer }
1376
+ total_tokens: { type: integer }
1377
+ AnthropicMessageRequest:
1378
+ type: object
1379
+ required: [model, messages, max_tokens]
1380
+ properties:
1381
+ model: { type: string }
1382
+ system:
1383
+ oneOf:
1384
+ - { type: string }
1385
+ - { type: array, items: { type: object } }
1386
+ messages:
1387
+ type: array
1388
+ minItems: 1
1389
+ items:
1390
+ type: object
1391
+ required: [role, content]
1392
+ properties:
1393
+ role: { type: string, enum: [user, assistant] }
1394
+ content:
1395
+ oneOf:
1396
+ - { type: string }
1397
+ - { type: array, items: { type: object } }
1398
+ max_tokens: { type: integer }
1399
+ temperature: { type: number }
1400
+ top_p: { type: number }
1401
+ stop_sequences: { type: array, items: { type: string } }
1402
+ tools: { type: array, items: { type: object } }
1403
+ tool_choice: { type: object }
1404
+ stream: { type: boolean, default: false }
1405
+ AnthropicMessage:
1406
+ type: object
1407
+ required: [id, type, role, model, content, stop_reason, usage]
1408
+ properties:
1409
+ id: { type: string }
1410
+ type: { type: string, const: message }
1411
+ role: { type: string, const: assistant }
1412
+ model: { type: string }
1413
+ content:
1414
+ type: array
1415
+ items:
1416
+ type: object
1417
+ required: [type]
1418
+ properties:
1419
+ type: { type: string, enum: [text, tool_use] }
1420
+ text: { type: string }
1421
+ id: { type: string }
1422
+ name: { type: string }
1423
+ input: { type: object }
1424
+ stop_reason: { type: string, enum: [end_turn, max_tokens, tool_use] }
1425
+ stop_sequence: { type: 'null' }
1426
+ usage:
1427
+ type: object
1428
+ properties:
1429
+ input_tokens: { type: integer }
1430
+ output_tokens: { type: integer }
1431
+ VideoCreateRequest:
1432
+ type: object
1433
+ required: [model, prompt]
1434
+ additionalProperties: false
1435
+ properties:
1436
+ model: { type: string, description: Video endpoint identifier from `/v1/models`. }
1437
+ prompt: { type: string }
1438
+ input_reference: { type: string, description: Reference image URL; mapped to the model's `image_url` / `image_urls`. }
1439
+ seconds:
1440
+ oneOf: [{ type: string }, { type: integer }]
1441
+ description: Clip length in seconds; mapped to `duration_seconds`.
1442
+ size: { type: string, pattern: '^[0-9]+x[0-9]+$', description: 'WIDTHxHEIGHT, mapped to `resolution` / `aspect_ratio` (or `width` / `height`).' }
1443
+ Video:
1444
+ type: object
1445
+ required: [id, object, model, status, progress, created_at]
1446
+ properties:
1447
+ id: { type: string, description: Task identifier; also accepted by `/api/v1/jobs/recordInfo`. }
1448
+ object: { type: string, const: video }
1449
+ model: { type: string }
1450
+ status: { type: string, enum: [queued, in_progress, completed, failed] }
1451
+ progress: { type: integer, minimum: 0, maximum: 100, description: 100 when completed, otherwise 0; no fabricated intermediate values. }
1452
+ created_at: { type: integer }
1453
+ completed_at: { type: integer }
1454
+ seconds: { type: string }
1455
+ size: { type: string }
1456
+ error:
1457
+ type: object
1458
+ required: [code, message]
1459
+ properties:
1460
+ code: { type: string }
1461
+ message: { type: string }