@spicyapi/sdk 0.1.0 → 0.2.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.
@@ -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
@@ -369,6 +457,272 @@ paths:
369
457
  '413': { $ref: '#/components/responses/PayloadTooLarge' }
370
458
  '429': { $ref: '#/components/responses/RateLimited' }
371
459
  '500': { $ref: '#/components/responses/ServerError' }
460
+ # ── OpenAI / Anthropic compatible surface ──────────────────────────────
461
+ # These operations use the response shapes of the external protocols they
462
+ # imitate (OpenAI error objects, Anthropic error objects, SSE event streams)
463
+ # instead of the `{code,msg,data,request_id}` envelope. Authentication, rate
464
+ # limits, validation, pricing and holds are shared with `/api/v1`.
465
+ /v1/models:
466
+ get:
467
+ tags: [OpenAI Compatible]
468
+ operationId: listOpenAIModels
469
+ summary: List callable models in the OpenAI `model` list shape
470
+ description: |
471
+ Returns every model that can be called right now. `id` is the SpicyAPI
472
+ model identifier accepted by every `/v1` and `/api/v1` operation;
473
+ `owned_by` is the model publisher (for example a research lab), never
474
+ an execution supplier. Pricing is not included; use `/api/v1/models`.
475
+ responses:
476
+ '200':
477
+ description: OpenAI-style model list.
478
+ content:
479
+ application/json:
480
+ schema:
481
+ $ref: '#/components/schemas/OpenAIModelList'
482
+ '401': { $ref: '#/components/responses/OpenAIError' }
483
+ '403': { $ref: '#/components/responses/OpenAIError' }
484
+ '429': { $ref: '#/components/responses/OpenAIError' }
485
+ '500': { $ref: '#/components/responses/OpenAIError' }
486
+ '503': { $ref: '#/components/responses/OpenAIError' }
487
+
488
+ /v1/chat/completions:
489
+ post:
490
+ tags: [OpenAI Compatible]
491
+ operationId: createChatCompletion
492
+ summary: OpenAI Chat Completions on a SpicyAPI text model
493
+ description: |
494
+ Accepts an OpenAI Chat Completions request. `messages`, `tools`,
495
+ `tool_choice`, `response_format`, `max_tokens` (or
496
+ `max_completion_tokens`), `temperature` and `top_p` map onto the
497
+ model's public input fields of the same name; `stream`,
498
+ `stream_options`, `user`, `metadata` and `store` are handled by this
499
+ layer. Any other parameter is forwarded to the model's input schema
500
+ and rejected with `400` when the model does not declare it — a
501
+ parameter is never dropped silently.
502
+
503
+ `stream: true` returns a Server-Sent Events stream of
504
+ `chat.completion.chunk` objects terminated by `data: [DONE]`.
505
+ `stream: false` returns one `chat.completion` object. The same hold,
506
+ settlement and refund rules as `/api/v1/jobs/stream` apply.
507
+ parameters:
508
+ - $ref: '#/components/parameters/IdempotencyKey'
509
+ requestBody:
510
+ required: true
511
+ content:
512
+ application/json:
513
+ schema:
514
+ $ref: '#/components/schemas/ChatCompletionRequest'
515
+ responses:
516
+ '200':
517
+ description: One `chat.completion` object, or an SSE stream when `stream` is true.
518
+ content:
519
+ application/json:
520
+ schema:
521
+ $ref: '#/components/schemas/ChatCompletion'
522
+ text/event-stream:
523
+ schema: { type: string }
524
+ '400': { $ref: '#/components/responses/OpenAIError' }
525
+ '401': { $ref: '#/components/responses/OpenAIError' }
526
+ '402': { $ref: '#/components/responses/OpenAIError' }
527
+ '403': { $ref: '#/components/responses/OpenAIError' }
528
+ '404': { $ref: '#/components/responses/OpenAIError' }
529
+ '409': { $ref: '#/components/responses/OpenAIError' }
530
+ '413': { $ref: '#/components/responses/OpenAIError' }
531
+ '429': { $ref: '#/components/responses/OpenAIError' }
532
+ '500': { $ref: '#/components/responses/OpenAIError' }
533
+ '503': { $ref: '#/components/responses/OpenAIError' }
534
+
535
+ /v1/responses:
536
+ post:
537
+ tags: [OpenAI Compatible]
538
+ operationId: createResponse
539
+ summary: OpenAI Responses API subset on a SpicyAPI text model
540
+ description: |
541
+ Accepts the stateless subset of the OpenAI Responses API: `model`,
542
+ `input` (string or item array with `message`, `function_call` and
543
+ `function_call_output` items), `instructions`, `max_output_tokens`,
544
+ `temperature`, `top_p`, `tools` (function tools), `tool_choice`,
545
+ `text.format`, `reasoning.effort` and `stream`. The request is
546
+ translated to a chat completion internally. `previous_response_id`
547
+ is rejected with `400`; send the full history in `input` instead.
548
+
549
+ Non-streaming calls return a `response` object with `output[]`
550
+ (`message` and `function_call` items) and `usage`. Streaming calls
551
+ emit `response.created`, `response.output_text.delta` and
552
+ `response.completed` events.
553
+ parameters:
554
+ - $ref: '#/components/parameters/IdempotencyKey'
555
+ requestBody:
556
+ required: true
557
+ content:
558
+ application/json:
559
+ schema:
560
+ $ref: '#/components/schemas/ResponsesRequest'
561
+ responses:
562
+ '200':
563
+ description: One `response` object, or an SSE stream when `stream` is true.
564
+ content:
565
+ application/json:
566
+ schema:
567
+ $ref: '#/components/schemas/ResponseObject'
568
+ text/event-stream:
569
+ schema: { type: string }
570
+ '400': { $ref: '#/components/responses/OpenAIError' }
571
+ '401': { $ref: '#/components/responses/OpenAIError' }
572
+ '402': { $ref: '#/components/responses/OpenAIError' }
573
+ '403': { $ref: '#/components/responses/OpenAIError' }
574
+ '404': { $ref: '#/components/responses/OpenAIError' }
575
+ '409': { $ref: '#/components/responses/OpenAIError' }
576
+ '413': { $ref: '#/components/responses/OpenAIError' }
577
+ '429': { $ref: '#/components/responses/OpenAIError' }
578
+ '500': { $ref: '#/components/responses/OpenAIError' }
579
+ '503': { $ref: '#/components/responses/OpenAIError' }
580
+
581
+ /v1/messages:
582
+ post:
583
+ tags: [OpenAI Compatible]
584
+ operationId: createMessage
585
+ summary: Anthropic Messages API on a SpicyAPI text model
586
+ description: |
587
+ Accepts an Anthropic Messages request (`model`, `system`, `messages`
588
+ with text / image / tool_use / tool_result blocks, `max_tokens`,
589
+ `temperature`, `top_p`, `stop_sequences`, `tools`, `tool_choice`,
590
+ `stream`) and translates it to a chat completion internally.
591
+ Non-streaming calls return an Anthropic `message` object. Streaming
592
+ calls emit `message_start`, `content_block_start`,
593
+ `content_block_delta`, `content_block_stop`, `message_delta` and
594
+ `message_stop` events. Errors use the Anthropic
595
+ `{type:"error",error:{type,message}}` shape.
596
+ parameters:
597
+ - $ref: '#/components/parameters/IdempotencyKey'
598
+ requestBody:
599
+ required: true
600
+ content:
601
+ application/json:
602
+ schema:
603
+ $ref: '#/components/schemas/AnthropicMessageRequest'
604
+ responses:
605
+ '200':
606
+ description: One Anthropic `message` object, or an SSE stream when `stream` is true.
607
+ content:
608
+ application/json:
609
+ schema:
610
+ $ref: '#/components/schemas/AnthropicMessage'
611
+ text/event-stream:
612
+ schema: { type: string }
613
+ '400': { $ref: '#/components/responses/AnthropicError' }
614
+ '401': { $ref: '#/components/responses/AnthropicError' }
615
+ '402': { $ref: '#/components/responses/AnthropicError' }
616
+ '403': { $ref: '#/components/responses/AnthropicError' }
617
+ '404': { $ref: '#/components/responses/AnthropicError' }
618
+ '409': { $ref: '#/components/responses/AnthropicError' }
619
+ '413': { $ref: '#/components/responses/AnthropicError' }
620
+ '429': { $ref: '#/components/responses/AnthropicError' }
621
+ '500': { $ref: '#/components/responses/AnthropicError' }
622
+ '503': { $ref: '#/components/responses/AnthropicError' }
623
+
624
+ /v1/videos:
625
+ post:
626
+ tags: [OpenAI Compatible]
627
+ operationId: createVideo
628
+ summary: Create a video generation task in the OpenAI Videos shape
629
+ description: |
630
+ Projection of `createTask` onto the OpenAI Videos API. `model` must be
631
+ a video endpoint identifier from `/v1/models`. `prompt` maps to the
632
+ model's `prompt`; `input_reference` (an HTTPS URL or a committed
633
+ `spicy://` upload) maps to `image_url` / `image_urls`; `seconds` maps to
634
+ `duration_seconds`; `size` (`WIDTHxHEIGHT`) maps to `resolution` and/or
635
+ `aspect_ratio`, or to `width` / `height` when the model declares pixel
636
+ dimensions. A parameter the selected model does not declare is
637
+ rejected with `400`. The same hold and idempotency rules as
638
+ `createTask` apply; the returned object never contains prices.
639
+ parameters:
640
+ - $ref: '#/components/parameters/IdempotencyKey'
641
+ requestBody:
642
+ required: true
643
+ content:
644
+ application/json:
645
+ schema:
646
+ $ref: '#/components/schemas/VideoCreateRequest'
647
+ responses:
648
+ '200':
649
+ description: The accepted task as a `video` object in `queued` state (an idempotent replay reports the current state).
650
+ content:
651
+ application/json:
652
+ schema:
653
+ $ref: '#/components/schemas/Video'
654
+ '400': { $ref: '#/components/responses/OpenAIError' }
655
+ '401': { $ref: '#/components/responses/OpenAIError' }
656
+ '402': { $ref: '#/components/responses/OpenAIError' }
657
+ '403': { $ref: '#/components/responses/OpenAIError' }
658
+ '404': { $ref: '#/components/responses/OpenAIError' }
659
+ '409': { $ref: '#/components/responses/OpenAIError' }
660
+ '413': { $ref: '#/components/responses/OpenAIError' }
661
+ '429': { $ref: '#/components/responses/OpenAIError' }
662
+ '500': { $ref: '#/components/responses/OpenAIError' }
663
+ '503': { $ref: '#/components/responses/OpenAIError' }
664
+
665
+ /v1/videos/{videoId}:
666
+ get:
667
+ tags: [OpenAI Compatible]
668
+ operationId: retrieveVideo
669
+ summary: Retrieve a video task as an OpenAI `video` object
670
+ description: |
671
+ Task states map to `queued` → `queued`, `running` → `in_progress`,
672
+ `succeeded` → `completed`, and `failed` / `canceled` / `expired` →
673
+ `failed` with an `error` object. Only the account and API key that
674
+ created the task can read it; other identifiers return `404`.
675
+ parameters:
676
+ - name: videoId
677
+ in: path
678
+ required: true
679
+ schema: { type: string }
680
+ responses:
681
+ '200':
682
+ description: Current video task state.
683
+ content:
684
+ application/json:
685
+ schema:
686
+ $ref: '#/components/schemas/Video'
687
+ '401': { $ref: '#/components/responses/OpenAIError' }
688
+ '403': { $ref: '#/components/responses/OpenAIError' }
689
+ '404': { $ref: '#/components/responses/OpenAIError' }
690
+ '429': { $ref: '#/components/responses/OpenAIError' }
691
+ '500': { $ref: '#/components/responses/OpenAIError' }
692
+ '503': { $ref: '#/components/responses/OpenAIError' }
693
+
694
+ /v1/videos/{videoId}/content:
695
+ get:
696
+ tags: [OpenAI Compatible]
697
+ operationId: downloadVideoContent
698
+ summary: Redirect to the generated video file
699
+ description: |
700
+ Responds `302 Found` with a short-lived signed download URL for the
701
+ task's primary output, the same address `/api/v1/common/download-url`
702
+ would issue. `409` while the task is still queued or running; `404`
703
+ when the task failed or has no downloadable output.
704
+ parameters:
705
+ - name: videoId
706
+ in: path
707
+ required: true
708
+ schema: { type: string }
709
+ responses:
710
+ '302':
711
+ description: Redirect to the signed media URL. The redirect must not be cached.
712
+ headers:
713
+ Location:
714
+ description: Short-lived signed download URL.
715
+ schema: { type: string, format: uri }
716
+ Cache-Control:
717
+ schema: { type: string, const: no-store }
718
+ '401': { $ref: '#/components/responses/OpenAIError' }
719
+ '403': { $ref: '#/components/responses/OpenAIError' }
720
+ '404': { $ref: '#/components/responses/OpenAIError' }
721
+ '409': { $ref: '#/components/responses/OpenAIError' }
722
+ '429': { $ref: '#/components/responses/OpenAIError' }
723
+ '500': { $ref: '#/components/responses/OpenAIError' }
724
+ '503': { $ref: '#/components/responses/OpenAIError' }
725
+
372
726
  components:
373
727
  securitySchemes:
374
728
  bearerAuth:
@@ -395,7 +749,10 @@ components:
395
749
  schema: { type: integer, minimum: 1 }
396
750
  responses:
397
751
  BadRequest:
398
- description: Invalid JSON, query, callback URL, model input, or media declaration.
752
+ description: >-
753
+ Invalid JSON, query, callback URL, model input, or media declaration.
754
+ Invalid callBackUrl values return code 400 and msg "Invalid callback URL"
755
+ with request_id. Submitted hosts, IP addresses, and DNS diagnostics are not echoed.
399
756
  content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
400
757
  Unauthorized:
401
758
  description: Missing, malformed, expired, revoked, or otherwise invalid API key.
@@ -428,6 +785,12 @@ components:
428
785
  Unavailable:
429
786
  description: No usable deployment or effective customer price exists for the selected model (`50301`).
430
787
  content: { application/json: { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } }
788
+ OpenAIError:
789
+ description: Error in the OpenAI `{error:{message,type,param,code}}` shape. The HTTP status is the same one `/api/v1` would return.
790
+ content: { application/json: { schema: { $ref: '#/components/schemas/OpenAIErrorBody' } } }
791
+ AnthropicError:
792
+ description: Error in the Anthropic `{type:"error",error:{type,message}}` shape.
793
+ content: { application/json: { schema: { $ref: '#/components/schemas/AnthropicErrorBody' } } }
431
794
  schemas:
432
795
  EnvelopeBase:
433
796
  type: object
@@ -445,7 +808,7 @@ components:
445
808
  properties:
446
809
  code:
447
810
  type: integer
448
- enum: [400, 401, 40201, 40202, 403, 40301, 40302, 40303, 404, 409, 413, 429, 500, 50301]
811
+ enum: [400, 401, 40201, 40202, 403, 40301, 40302, 40303, 404, 409, 40901, 413, 429, 500, 50301]
449
812
  msg: { type: string }
450
813
  CreateTaskRequest:
451
814
  type: object
@@ -463,11 +826,39 @@ components:
463
826
  callBackUrl:
464
827
  type: string
465
828
  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.
829
+ description: >-
830
+ Optional public HTTP(S) endpoint for terminal task delivery; HTTPS is recommended.
831
+ Only ports 80 and 443 are allowed, and URL credentials are rejected.
832
+ The hostname must resolve to public IP addresses and is checked again when connecting.
833
+ Invalid values return HTTP 400 with code 400, msg "Invalid callback URL", and request_id.
834
+ quoteId:
835
+ type: string
836
+ maxLength: 4096
837
+ description: Signed quote returned by jobs/quote for this same account, API key and request. Does not replace Idempotency-Key.
838
+ expectedCost:
839
+ allOf:
840
+ - $ref: '#/components/schemas/USDString'
841
+ description: Confirmed amount to reserve, with up to nine decimal places. A changed price returns business code 40901 before reservation.
842
+ TaskQuoteResponse:
843
+ type: object
844
+ required: [quoteId, model, estimatedCost, maxCharge, currency, quantity, unit, expiresAt]
845
+ additionalProperties: false
846
+ properties:
847
+ quoteId: { type: string }
848
+ model: { type: string, description: Resolved endpoint model identifier. }
849
+ estimatedCost: { $ref: '#/components/schemas/USDString' }
850
+ maxCharge: { $ref: '#/components/schemas/USDString' }
851
+ currency: { type: string, const: USD }
852
+ quantity: { type: string, description: Estimated billable quantity. }
853
+ unit: { type: string }
854
+ expiresAt: { type: string, format: date-time }
855
+ TaskQuoteEnvelope:
856
+ allOf:
857
+ - $ref: '#/components/schemas/EnvelopeBase'
858
+ - type: object
859
+ required: [data]
860
+ properties:
861
+ data: { $ref: '#/components/schemas/TaskQuoteResponse' }
471
862
  CreateTaskResponse:
472
863
  type: object
473
864
  required: [taskId, state, estimatedCost]
@@ -480,7 +871,7 @@ components:
480
871
  estimatedCost:
481
872
  allOf:
482
873
  - $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.
874
+ 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
875
  TaskActionRequest:
485
876
  type: object
486
877
  required: [taskId]
@@ -527,11 +918,11 @@ components:
527
918
  type: object
528
919
  additionalProperties: true
529
920
  output:
530
- description: Normalized model output when available. Omitted after retention redaction.
921
+ description: Normalized first-party model output when available. Asset entries contain SpicyAPI object keys, never execution-channel URLs or raw responses. Omitted after retention redaction.
531
922
  type: object
532
923
  additionalProperties: true
533
- errorCode: { type: string }
534
- errorMessage: { type: string }
924
+ errorCode: { type: string, description: Stable SpicyAPI failure identifier; never a raw execution-channel code. }
925
+ errorMessage: { type: string, description: Safe normalized explanation without internal service names, hosts, task IDs, or raw errors. }
535
926
  cost: { $ref: '#/components/schemas/USDString' }
536
927
  settled:
537
928
  type: boolean
@@ -586,6 +977,10 @@ components:
586
977
  description: Value used by `input.resolution`; empty when the model has one price tier.
587
978
  unit: { type: string, enum: [per_image, per_second, per_request, per_1k_tokens] }
588
979
  price: { $ref: '#/components/schemas/USDString' }
980
+ regularPrice: { $ref: '#/components/schemas/USDString' }
981
+ offerLabel: { type: string }
982
+ offerPercent: { type: string }
983
+ offerEndsAt: { type: string, format: date-time }
589
984
  currency: { type: string, const: USD }
590
985
  APIModel:
591
986
  type: object
@@ -596,7 +991,9 @@ components:
596
991
  type: string
597
992
  description: Stable product-family identifier; endpoint variants are listed in `tasks` and expressed by `inputSchema`.
598
993
  displayName: { type: string }
599
- provider: { type: string }
994
+ provider:
995
+ type: string
996
+ description: Model creator/author identifier. It never identifies an execution channel.
600
997
  modality: { type: string, enum: [image, video, audio, text] }
601
998
  tasks: { type: array, items: { type: string } }
602
999
  async: { type: boolean }
@@ -614,7 +1011,10 @@ components:
614
1011
  maxOutputDurationSeconds:
615
1012
  type: integer
616
1013
  minimum: 1
617
- description: Maximum `input.duration` accepted by the model schema; omitted when the model has no declared duration maximum.
1014
+ description: >-
1015
+ Maximum output duration the model schema allows, taken from the `maximum` of its
1016
+ `duration_seconds` property; omitted when that property is absent or has no declared
1017
+ maximum. Distinct from `taskTimeoutSeconds`, which is the platform execution deadline.
618
1018
  enabled: { type: boolean }
619
1019
  available: { type: boolean }
620
1020
  quantityField: { type: string }
@@ -622,11 +1022,22 @@ components:
622
1022
  startingPrice: { $ref: '#/components/schemas/APIModelPrice' }
623
1023
  inputSchema:
624
1024
  type: object
625
- description: JSON Schema draft 2020-12 used by `createTask.input` validation.
1025
+ description: >-
1026
+ JSON Schema draft 2020-12 used by `createTask.input` validation and Playground rendering.
1027
+ Standard JSON Schema keywords validate. Presentation hints live only in a per-property `x-ui`
1028
+ object whose keys are snake_case and drawn from a closed set: `widget`, `order`, `label`,
1029
+ `placeholder`, `rows`, `step`, `unit`, `accept`, `max_size_mb`, `advanced`, `primary`,
1030
+ `affects_price`, `visible_when`, `enum_labels`. `x-ui.order` is the only source of field
1031
+ order (descending first). `x-ui.widget` is one of `textarea`, `text`, `upload`,
1032
+ `multi-upload`, `select`, `radio`, `slider`, `number`, `switch`, `json`, `object-list`,
1033
+ `chat-messages`, `hidden`. Root-level composition is limited to `anyOf`, where every branch
1034
+ contains only `required`, and `allOf`, where each branch is `if` (`required` or
1035
+ `properties.*.const`) plus `then` (`required`); `oneOf`, `not` and `dependentSchemas` never
1036
+ appear. Unknown hints must be ignored.
626
1037
  additionalProperties: true
627
1038
  version:
628
1039
  type: string
629
- description: Provider/model version label used to detect catalog changes.
1040
+ description: Public model-catalog version label used to detect schema or metadata changes.
630
1041
  availability:
631
1042
  type: string
632
1043
  enum: [planned, available, preview, maintenance]
@@ -681,8 +1092,8 @@ components:
681
1092
  required: [contentType, bytes]
682
1093
  additionalProperties: false
683
1094
  properties:
684
- contentType: { type: string, enum: [image/jpeg, image/png, image/webp, image/gif] }
685
- bytes: { type: integer, minimum: 1, maximum: 10485760 }
1095
+ contentType: { type: string, enum: [image/jpeg, image/png, image/webp, image/gif, video/mp4, video/webm, audio/mpeg, audio/wav] }
1096
+ bytes: { type: integer, minimum: 1, maximum: 104857600, description: Images are limited to 10 MiB; supported audio/video to 100 MiB. }
686
1097
  UploadURLResponse:
687
1098
  type: object
688
1099
  required: [fileId, key, uploadUrl, method, headers, expiresAt, maxBytes]
@@ -710,10 +1121,13 @@ components:
710
1121
  fileId: { type: string, pattern: '^fil_' }
711
1122
  status: { type: string, const: ready }
712
1123
  bytes: { type: integer, minimum: 1 }
713
- contentType: { type: string, enum: [image/jpeg, image/png, image/webp, image/gif] }
1124
+ contentType: { type: string, enum: [image/jpeg, image/png, image/webp, image/gif, video/mp4, video/webm, audio/mpeg, audio/wav] }
714
1125
  sha256: { type: string, pattern: '^[a-f0-9]{64}$' }
715
1126
  uri: { type: string, pattern: '^spicy://f/fil_' }
716
1127
  expiresAt: { type: string, format: date-time }
1128
+ durationSeconds: { type: string, description: Measured audio/video duration in seconds. }
1129
+ width: { type: integer, minimum: 0 }
1130
+ height: { type: integer, minimum: 0 }
717
1131
  FileCommitEnvelope:
718
1132
  allOf:
719
1133
  - $ref: '#/components/schemas/EnvelopeBase'
@@ -750,3 +1164,271 @@ components:
750
1164
  type: string
751
1165
  pattern: '^-?[0-9]+(?:\.[0-9]+)?$'
752
1166
  description: Decimal USD amount encoded as a string to avoid binary floating-point loss.
1167
+
1168
+ # ── OpenAI / Anthropic compatible shapes ───────────────────────────────
1169
+ OpenAIErrorBody:
1170
+ type: object
1171
+ required: [error]
1172
+ properties:
1173
+ error:
1174
+ type: object
1175
+ required: [message, type, code]
1176
+ properties:
1177
+ message: { type: string }
1178
+ type:
1179
+ type: string
1180
+ enum: [invalid_request_error, authentication_error, permission_error, insufficient_quota, rate_limit_error, server_error]
1181
+ param: { type: [string, 'null'] }
1182
+ code:
1183
+ type: string
1184
+ description: Stable machine-readable reason, for example `invalid_request`, `invalid_api_key`, `insufficient_balance`, `model_not_allowed`, `model_unavailable`, `rate_limit_exceeded`.
1185
+ AnthropicErrorBody:
1186
+ type: object
1187
+ required: [type, error]
1188
+ properties:
1189
+ type: { type: string, const: error }
1190
+ error:
1191
+ type: object
1192
+ required: [type, message]
1193
+ properties:
1194
+ type:
1195
+ type: string
1196
+ enum: [invalid_request_error, authentication_error, billing_error, permission_error, not_found_error, request_too_large, rate_limit_error, api_error, overloaded_error]
1197
+ message: { type: string }
1198
+ OpenAIModelList:
1199
+ type: object
1200
+ required: [object, data]
1201
+ properties:
1202
+ object: { type: string, const: list }
1203
+ data:
1204
+ type: array
1205
+ items:
1206
+ type: object
1207
+ required: [id, object, created, owned_by]
1208
+ properties:
1209
+ id: { type: string, description: SpicyAPI model identifier; may contain slashes. }
1210
+ object: { type: string, const: model }
1211
+ created: { type: integer, description: Unix seconds of the last catalog update. }
1212
+ owned_by: { type: string, description: Model publisher. }
1213
+ ChatCompletionRequest:
1214
+ type: object
1215
+ required: [model, messages]
1216
+ properties:
1217
+ model: { type: string }
1218
+ messages:
1219
+ type: array
1220
+ minItems: 1
1221
+ items:
1222
+ type: object
1223
+ required: [role]
1224
+ properties:
1225
+ role: { type: string }
1226
+ content:
1227
+ oneOf:
1228
+ - { type: [string, 'null'] }
1229
+ - { type: array, items: { type: object, additionalProperties: true } }
1230
+ tool_call_id: { type: string }
1231
+ tool_calls: { type: array, items: { type: object, additionalProperties: true } }
1232
+ reasoning_content:
1233
+ type: string
1234
+ description: Optional assistant reasoning text; availability and message roles follow the selected model input schema.
1235
+ additionalProperties: true
1236
+ stream: { type: boolean, default: false }
1237
+ max_tokens: { type: integer }
1238
+ max_completion_tokens: { type: integer, description: Alias of `max_tokens`. }
1239
+ temperature: { type: number }
1240
+ top_p: { type: number }
1241
+ tools: { type: array, items: { type: object, additionalProperties: true } }
1242
+ tool_choice: { oneOf: [{ type: string }, { type: object, additionalProperties: true }] }
1243
+ response_format: { type: object, additionalProperties: true }
1244
+ additionalProperties:
1245
+ description: Forwarded to the model input schema and rejected when undeclared.
1246
+ ChatCompletion:
1247
+ type: object
1248
+ required: [id, object, created, model, choices]
1249
+ properties:
1250
+ id: { type: string }
1251
+ object: { type: string, const: chat.completion }
1252
+ created: { type: integer }
1253
+ model: { type: string }
1254
+ choices:
1255
+ type: array
1256
+ items:
1257
+ type: object
1258
+ required: [index, message, finish_reason]
1259
+ properties:
1260
+ index: { type: integer }
1261
+ message:
1262
+ type: object
1263
+ properties:
1264
+ role: { type: string }
1265
+ content: { type: [string, 'null'] }
1266
+ refusal: { type: string }
1267
+ reasoning_content:
1268
+ type: string
1269
+ description: Optional reasoning text returned by models that expose it. Streaming chunks use the same field in delta.
1270
+ tool_calls: { type: array, items: { type: object, additionalProperties: true } }
1271
+ finish_reason: { type: string }
1272
+ logprobs: { type: 'null' }
1273
+ usage:
1274
+ type: object
1275
+ properties:
1276
+ prompt_tokens: { type: integer }
1277
+ completion_tokens: { type: integer }
1278
+ total_tokens: { type: integer }
1279
+ prompt_tokens_details:
1280
+ type: object
1281
+ properties:
1282
+ cached_tokens: { type: integer }
1283
+ cache_creation_input_tokens: { type: integer }
1284
+ cache_read_input_tokens: { type: integer }
1285
+ completion_tokens_details:
1286
+ type: object
1287
+ properties:
1288
+ reasoning_tokens:
1289
+ type: integer
1290
+ description: Included in completion_tokens; do not add it again when calculating usage.
1291
+ ResponsesRequest:
1292
+ type: object
1293
+ required: [model, input]
1294
+ properties:
1295
+ model: { type: string }
1296
+ input:
1297
+ oneOf:
1298
+ - { type: string }
1299
+ - { type: array, items: { type: object } }
1300
+ instructions: { type: string }
1301
+ max_output_tokens: { type: integer }
1302
+ temperature: { type: number }
1303
+ top_p: { type: number }
1304
+ tools: { type: array, items: { type: object } }
1305
+ tool_choice: { oneOf: [{ type: string }, { type: object }] }
1306
+ text: { type: object }
1307
+ reasoning: { type: object, properties: { effort: { type: string } } }
1308
+ stream: { type: boolean, default: false }
1309
+ ResponseObject:
1310
+ type: object
1311
+ required: [id, object, created_at, status, model, output]
1312
+ properties:
1313
+ id: { type: string }
1314
+ object: { type: string, const: response }
1315
+ created_at: { type: integer }
1316
+ status: { type: string, enum: [completed, incomplete, in_progress] }
1317
+ model: { type: string }
1318
+ output:
1319
+ type: array
1320
+ items:
1321
+ type: object
1322
+ required: [id, type, status]
1323
+ properties:
1324
+ id: { type: string }
1325
+ type: { type: string, enum: [message, function_call] }
1326
+ status: { type: string }
1327
+ role: { type: string }
1328
+ content:
1329
+ type: array
1330
+ items:
1331
+ type: object
1332
+ properties:
1333
+ type: { type: string, const: output_text }
1334
+ text: { type: string }
1335
+ annotations: { type: array, items: { type: object } }
1336
+ call_id: { type: string }
1337
+ name: { type: string }
1338
+ arguments: { type: string }
1339
+ error: { type: 'null' }
1340
+ incomplete_details:
1341
+ type: [object, 'null']
1342
+ properties:
1343
+ reason: { type: string }
1344
+ usage:
1345
+ type: object
1346
+ properties:
1347
+ input_tokens: { type: integer }
1348
+ output_tokens: { type: integer }
1349
+ total_tokens: { type: integer }
1350
+ AnthropicMessageRequest:
1351
+ type: object
1352
+ required: [model, messages, max_tokens]
1353
+ properties:
1354
+ model: { type: string }
1355
+ system:
1356
+ oneOf:
1357
+ - { type: string }
1358
+ - { type: array, items: { type: object } }
1359
+ messages:
1360
+ type: array
1361
+ minItems: 1
1362
+ items:
1363
+ type: object
1364
+ required: [role, content]
1365
+ properties:
1366
+ role: { type: string, enum: [user, assistant] }
1367
+ content:
1368
+ oneOf:
1369
+ - { type: string }
1370
+ - { type: array, items: { type: object } }
1371
+ max_tokens: { type: integer }
1372
+ temperature: { type: number }
1373
+ top_p: { type: number }
1374
+ stop_sequences: { type: array, items: { type: string } }
1375
+ tools: { type: array, items: { type: object } }
1376
+ tool_choice: { type: object }
1377
+ stream: { type: boolean, default: false }
1378
+ AnthropicMessage:
1379
+ type: object
1380
+ required: [id, type, role, model, content, stop_reason, usage]
1381
+ properties:
1382
+ id: { type: string }
1383
+ type: { type: string, const: message }
1384
+ role: { type: string, const: assistant }
1385
+ model: { type: string }
1386
+ content:
1387
+ type: array
1388
+ items:
1389
+ type: object
1390
+ required: [type]
1391
+ properties:
1392
+ type: { type: string, enum: [text, tool_use] }
1393
+ text: { type: string }
1394
+ id: { type: string }
1395
+ name: { type: string }
1396
+ input: { type: object }
1397
+ stop_reason: { type: string, enum: [end_turn, max_tokens, tool_use] }
1398
+ stop_sequence: { type: 'null' }
1399
+ usage:
1400
+ type: object
1401
+ properties:
1402
+ input_tokens: { type: integer }
1403
+ output_tokens: { type: integer }
1404
+ VideoCreateRequest:
1405
+ type: object
1406
+ required: [model, prompt]
1407
+ additionalProperties: false
1408
+ properties:
1409
+ model: { type: string, description: Video endpoint identifier from `/v1/models`. }
1410
+ prompt: { type: string }
1411
+ input_reference: { type: string, description: Reference image URL; mapped to the model's `image_url` / `image_urls`. }
1412
+ seconds:
1413
+ oneOf: [{ type: string }, { type: integer }]
1414
+ description: Clip length in seconds; mapped to `duration_seconds`.
1415
+ size: { type: string, pattern: '^[0-9]+x[0-9]+$', description: 'WIDTHxHEIGHT, mapped to `resolution` / `aspect_ratio` (or `width` / `height`).' }
1416
+ Video:
1417
+ type: object
1418
+ required: [id, object, model, status, progress, created_at]
1419
+ properties:
1420
+ id: { type: string, description: Task identifier; also accepted by `/api/v1/jobs/recordInfo`. }
1421
+ object: { type: string, const: video }
1422
+ model: { type: string }
1423
+ status: { type: string, enum: [queued, in_progress, completed, failed] }
1424
+ progress: { type: integer, minimum: 0, maximum: 100, description: 100 when completed, otherwise 0; no fabricated intermediate values. }
1425
+ created_at: { type: integer }
1426
+ completed_at: { type: integer }
1427
+ seconds: { type: string }
1428
+ size: { type: string }
1429
+ error:
1430
+ type: object
1431
+ required: [code, message]
1432
+ properties:
1433
+ code: { type: string }
1434
+ message: { type: string }