@giveitsmaller/contracts 0.2.0 → 0.2.3
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.
- package/asyncapi/events.yaml +2012 -0
- package/dist/openapi/models/MultipartCompleteRequest.d.ts +1 -1
- package/dist/openapi/models/MultipartCompleteRequest.js +3 -3
- package/dist/openapi/models/MultipartCompleteResponse.d.ts +50 -0
- package/dist/openapi/models/MultipartCompleteResponse.js +53 -0
- package/dist/openapi/models/MultipartCompleteSuccessEnvelope.d.ts +46 -0
- package/dist/openapi/models/MultipartCompleteSuccessEnvelope.js +54 -0
- package/dist/openapi/models/MultipartInitiateResponse.d.ts +1 -1
- package/dist/openapi/models/MultipartInitiateResponse.js +3 -3
- package/dist/openapi/models/index.d.ts +2 -0
- package/dist/openapi/models/index.js +2 -0
- package/openapi/api.yaml +2533 -0
- package/operations/schemas/archive.yaml +22 -0
- package/operations/schemas/compress.yaml +280 -0
- package/operations/schemas/convert.yaml +104 -0
- package/operations/schemas/merge.yaml +211 -0
- package/operations/schemas/thumbnail.yaml +120 -0
- package/operations/schemas/watermark.yaml +87 -0
- package/package.json +14 -3
package/openapi/api.yaml
ADDED
|
@@ -0,0 +1,2533 @@
|
|
|
1
|
+
openapi: 3.1.0
|
|
2
|
+
info:
|
|
3
|
+
title: GISL Compression API
|
|
4
|
+
description: |
|
|
5
|
+
REST API for the GISL (Give It Smaller) file compression and processing service.
|
|
6
|
+
|
|
7
|
+
**Architecture:**
|
|
8
|
+
- Upload files to get a `file_id`
|
|
9
|
+
- Create workflows referencing uploaded files with operations (compress, thumbnail, watermark, merge, archive, convert)
|
|
10
|
+
- Poll status, stream SSE events, or receive webhook callbacks
|
|
11
|
+
- Download results per operation output
|
|
12
|
+
|
|
13
|
+
**Response envelope:**
|
|
14
|
+
All mutation and query endpoints return `{ success: true, data: {...} }` on success
|
|
15
|
+
and `{ success: false, error: "...", details: [...] }` on failure.
|
|
16
|
+
Exceptions: `GET /api/operations/schema` returns raw JSON (CDN-cacheable),
|
|
17
|
+
health probes return flat objects, and `POST /api/contact` returns 204 with no body.
|
|
18
|
+
version: 2.0.0
|
|
19
|
+
contact:
|
|
20
|
+
name: API Support
|
|
21
|
+
|
|
22
|
+
servers:
|
|
23
|
+
- url: http://localhost:8080
|
|
24
|
+
description: Local development
|
|
25
|
+
- url: https://api.staging.giveitsmaller.com
|
|
26
|
+
description: Staging environment
|
|
27
|
+
|
|
28
|
+
tags:
|
|
29
|
+
- name: Upload
|
|
30
|
+
description: File upload operations (single and multipart)
|
|
31
|
+
- name: Workflow
|
|
32
|
+
description: Workflow creation, status, download, and real-time events
|
|
33
|
+
- name: Operations
|
|
34
|
+
description: Operation schema introspection and retry
|
|
35
|
+
- name: Health
|
|
36
|
+
description: Health check endpoints
|
|
37
|
+
- name: Contact
|
|
38
|
+
description: Contact form submissions
|
|
39
|
+
|
|
40
|
+
paths:
|
|
41
|
+
# ============================================
|
|
42
|
+
# UPLOAD ENDPOINTS
|
|
43
|
+
# ============================================
|
|
44
|
+
|
|
45
|
+
/api/uploads:
|
|
46
|
+
post:
|
|
47
|
+
summary: Upload a file
|
|
48
|
+
description: |
|
|
49
|
+
Upload a single file for later use in workflows. Returns a `file_id` that can be
|
|
50
|
+
referenced when creating workflows.
|
|
51
|
+
|
|
52
|
+
No job or workflow is created — the file is stored in S3 and persisted in the
|
|
53
|
+
`uploads` table. Create a workflow via `POST /api/workflows` to process it.
|
|
54
|
+
|
|
55
|
+
For large files, use the multipart upload flow instead
|
|
56
|
+
(`POST /api/uploads/multipart/initiate`).
|
|
57
|
+
operationId: uploadFile
|
|
58
|
+
tags:
|
|
59
|
+
- Upload
|
|
60
|
+
requestBody:
|
|
61
|
+
required: true
|
|
62
|
+
content:
|
|
63
|
+
multipart/form-data:
|
|
64
|
+
schema:
|
|
65
|
+
$ref: '#/components/schemas/SingleUploadRequest'
|
|
66
|
+
responses:
|
|
67
|
+
'200':
|
|
68
|
+
description: File uploaded successfully
|
|
69
|
+
content:
|
|
70
|
+
application/json:
|
|
71
|
+
schema:
|
|
72
|
+
$ref: '#/components/schemas/UploadSuccessEnvelope'
|
|
73
|
+
example:
|
|
74
|
+
success: true
|
|
75
|
+
data:
|
|
76
|
+
file_id: "019539ab-1111-7000-8000-000000000001"
|
|
77
|
+
original_name: "photo.jpg"
|
|
78
|
+
mime_type: "image/jpeg"
|
|
79
|
+
size_bytes: 2457600
|
|
80
|
+
'400':
|
|
81
|
+
description: Validation failed (missing file, invalid filename)
|
|
82
|
+
content:
|
|
83
|
+
application/json:
|
|
84
|
+
schema:
|
|
85
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
86
|
+
example:
|
|
87
|
+
success: false
|
|
88
|
+
error: "File is required"
|
|
89
|
+
'413':
|
|
90
|
+
description: File exceeds maximum upload size
|
|
91
|
+
content:
|
|
92
|
+
application/json:
|
|
93
|
+
schema:
|
|
94
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
95
|
+
example:
|
|
96
|
+
success: false
|
|
97
|
+
error: "File size exceeds maximum allowed (500MB)"
|
|
98
|
+
'415':
|
|
99
|
+
description: Unsupported file type
|
|
100
|
+
content:
|
|
101
|
+
application/json:
|
|
102
|
+
schema:
|
|
103
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
104
|
+
example:
|
|
105
|
+
success: false
|
|
106
|
+
error: "Unsupported MIME type: application/x-msdownload"
|
|
107
|
+
'429':
|
|
108
|
+
description: Rate limit exceeded
|
|
109
|
+
content:
|
|
110
|
+
application/json:
|
|
111
|
+
schema:
|
|
112
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
113
|
+
'500':
|
|
114
|
+
description: Internal server error
|
|
115
|
+
content:
|
|
116
|
+
application/json:
|
|
117
|
+
schema:
|
|
118
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
119
|
+
|
|
120
|
+
/api/uploads/multipart/initiate:
|
|
121
|
+
post:
|
|
122
|
+
summary: Initiate direct S3 multipart upload
|
|
123
|
+
description: |
|
|
124
|
+
Start a direct-to-S3 chunked upload. The client sends the first chunk (recommended
|
|
125
|
+
8MB) and the total file size. The API uses the first chunk for:
|
|
126
|
+
- MIME type detection (to validate supported formats)
|
|
127
|
+
- Upload throughput measurement (to calculate optimal chunk size)
|
|
128
|
+
|
|
129
|
+
The API stores the first chunk as S3 multipart upload part 1, calculates the
|
|
130
|
+
optimal chunk size and total number of parts, then returns pre-signed PUT URLs
|
|
131
|
+
for the remaining parts. The client uploads parts 2-N directly to S3.
|
|
132
|
+
|
|
133
|
+
**Chunk sizing strategy:**
|
|
134
|
+
- First chunk: fixed 8MB (sent to API)
|
|
135
|
+
- Remaining chunks: API calculates `recommended_chunk_size` from throughput * 5s,
|
|
136
|
+
clamped to 5MB-100MB
|
|
137
|
+
- The last part may be smaller than 5MB (S3 allows this for the final part)
|
|
138
|
+
- Maximum 500 parts per upload
|
|
139
|
+
|
|
140
|
+
**Pre-signed URL TTL:**
|
|
141
|
+
Dynamic based on estimated upload duration * 2, clamped between 900s and 3600s.
|
|
142
|
+
|
|
143
|
+
After all parts are uploaded to S3, call the complete endpoint to finalise.
|
|
144
|
+
operationId: multipartInitiate
|
|
145
|
+
tags:
|
|
146
|
+
- Upload
|
|
147
|
+
requestBody:
|
|
148
|
+
required: true
|
|
149
|
+
content:
|
|
150
|
+
multipart/form-data:
|
|
151
|
+
schema:
|
|
152
|
+
$ref: '#/components/schemas/MultipartInitiateRequest'
|
|
153
|
+
responses:
|
|
154
|
+
'200':
|
|
155
|
+
description: Multipart upload initiated, pre-signed URLs returned
|
|
156
|
+
content:
|
|
157
|
+
application/json:
|
|
158
|
+
schema:
|
|
159
|
+
$ref: '#/components/schemas/MultipartInitiateSuccessEnvelope'
|
|
160
|
+
example:
|
|
161
|
+
success: true
|
|
162
|
+
data:
|
|
163
|
+
upload_id: "019539ab-1111-7000-8000-000000000001"
|
|
164
|
+
mime_type: "image/jpeg"
|
|
165
|
+
first_chunk_etag: '"d8e8fca2dc0f896fd7cb4cb0031ba249"'
|
|
166
|
+
first_chunk_size_bytes: 8388608
|
|
167
|
+
total_parts: 5
|
|
168
|
+
recommended_chunk_size: 10485760
|
|
169
|
+
presigned_urls:
|
|
170
|
+
- part_number: 2
|
|
171
|
+
url: "https://gisl-stg-euw1-input.s3.eu-west-1.amazonaws.com/uploads/...?X-Amz-Expires=1800&..."
|
|
172
|
+
expires_at: "2026-03-06T12:30:00.000Z"
|
|
173
|
+
- part_number: 3
|
|
174
|
+
url: "https://gisl-stg-euw1-input.s3.eu-west-1.amazonaws.com/uploads/...?X-Amz-Expires=1800&..."
|
|
175
|
+
expires_at: "2026-03-06T12:30:00.000Z"
|
|
176
|
+
'400':
|
|
177
|
+
description: Validation failed (missing fields, invalid file)
|
|
178
|
+
content:
|
|
179
|
+
application/json:
|
|
180
|
+
schema:
|
|
181
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
182
|
+
example:
|
|
183
|
+
success: false
|
|
184
|
+
error: "File is required"
|
|
185
|
+
'413':
|
|
186
|
+
description: File exceeds maximum upload size
|
|
187
|
+
content:
|
|
188
|
+
application/json:
|
|
189
|
+
schema:
|
|
190
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
191
|
+
'415':
|
|
192
|
+
description: Unsupported file type
|
|
193
|
+
content:
|
|
194
|
+
application/json:
|
|
195
|
+
schema:
|
|
196
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
197
|
+
'429':
|
|
198
|
+
description: Rate limit exceeded
|
|
199
|
+
content:
|
|
200
|
+
application/json:
|
|
201
|
+
schema:
|
|
202
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
203
|
+
'500':
|
|
204
|
+
description: Internal server error
|
|
205
|
+
content:
|
|
206
|
+
application/json:
|
|
207
|
+
schema:
|
|
208
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
209
|
+
|
|
210
|
+
/api/uploads/multipart/complete:
|
|
211
|
+
post:
|
|
212
|
+
summary: Complete direct S3 multipart upload
|
|
213
|
+
description: |
|
|
214
|
+
Finalise a direct-to-S3 multipart upload after all parts have been uploaded.
|
|
215
|
+
|
|
216
|
+
The client sends the `upload_id` (from the initiate response) and an array of
|
|
217
|
+
part ETags collected from the S3 PUT responses for parts 2-N. The API already
|
|
218
|
+
has the part 1 ETag from the initiate step.
|
|
219
|
+
|
|
220
|
+
The API calls S3 CompleteMultipartUpload and persists the upload record.
|
|
221
|
+
No job or workflow is created — use `POST /api/workflows` to process the file.
|
|
222
|
+
operationId: multipartComplete
|
|
223
|
+
tags:
|
|
224
|
+
- Upload
|
|
225
|
+
requestBody:
|
|
226
|
+
required: true
|
|
227
|
+
content:
|
|
228
|
+
application/json:
|
|
229
|
+
schema:
|
|
230
|
+
$ref: '#/components/schemas/MultipartCompleteRequest'
|
|
231
|
+
responses:
|
|
232
|
+
'201':
|
|
233
|
+
description: |
|
|
234
|
+
Upload completed successfully. The returned `upload_id` is the identifier
|
|
235
|
+
of the finalised upload and can be passed as `file_id` when creating
|
|
236
|
+
workflows via `POST /api/workflows`. File metadata (original name, MIME
|
|
237
|
+
type, size) is available from `GET /api/uploads/{id}/metadata` if the
|
|
238
|
+
client did not retain the values captured during initiate.
|
|
239
|
+
content:
|
|
240
|
+
application/json:
|
|
241
|
+
schema:
|
|
242
|
+
$ref: '#/components/schemas/MultipartCompleteSuccessEnvelope'
|
|
243
|
+
example:
|
|
244
|
+
success: true
|
|
245
|
+
data:
|
|
246
|
+
upload_id: "019539ab-1111-7000-8000-000000000001"
|
|
247
|
+
status: "completed"
|
|
248
|
+
'400':
|
|
249
|
+
description: Invalid upload_id, missing parts, or ETag mismatch
|
|
250
|
+
content:
|
|
251
|
+
application/json:
|
|
252
|
+
schema:
|
|
253
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
254
|
+
example:
|
|
255
|
+
success: false
|
|
256
|
+
error: "Missing ETags for parts: 3, 5"
|
|
257
|
+
'404':
|
|
258
|
+
description: Upload session not found or expired
|
|
259
|
+
content:
|
|
260
|
+
application/json:
|
|
261
|
+
schema:
|
|
262
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
263
|
+
example:
|
|
264
|
+
success: false
|
|
265
|
+
error: "Upload session not found or expired"
|
|
266
|
+
'500':
|
|
267
|
+
description: Internal server error
|
|
268
|
+
content:
|
|
269
|
+
application/json:
|
|
270
|
+
schema:
|
|
271
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
272
|
+
|
|
273
|
+
/api/uploads/{id}/metadata:
|
|
274
|
+
get:
|
|
275
|
+
summary: Get file metadata
|
|
276
|
+
description: |
|
|
277
|
+
Returns metadata extracted from an uploaded file. Useful for inspecting files
|
|
278
|
+
before building workflows (e.g. check dimensions to decide if resize is needed,
|
|
279
|
+
check codec to decide on compression options).
|
|
280
|
+
|
|
281
|
+
Fields vary by MIME type:
|
|
282
|
+
- **Images:** dimensions, color_space, dpi, has_alpha, exif, dominant_colors
|
|
283
|
+
- **Video:** dimensions, duration, codec, fps, audio_codec, bitrate
|
|
284
|
+
- **Audio:** duration, bitrate, channels, sample_rate, codec
|
|
285
|
+
- **Documents:** page_count, dimensions (for PDF)
|
|
286
|
+
operationId: getUploadMetadata
|
|
287
|
+
tags:
|
|
288
|
+
- Upload
|
|
289
|
+
parameters:
|
|
290
|
+
- name: id
|
|
291
|
+
in: path
|
|
292
|
+
required: true
|
|
293
|
+
description: Upload file ID (UUID v7)
|
|
294
|
+
schema:
|
|
295
|
+
$ref: '#/components/schemas/UuidV7'
|
|
296
|
+
responses:
|
|
297
|
+
'200':
|
|
298
|
+
description: File metadata retrieved
|
|
299
|
+
content:
|
|
300
|
+
application/json:
|
|
301
|
+
schema:
|
|
302
|
+
$ref: '#/components/schemas/MetadataSuccessEnvelope'
|
|
303
|
+
example:
|
|
304
|
+
success: true
|
|
305
|
+
data:
|
|
306
|
+
file_id: "019539ab-1111-7000-8000-000000000001"
|
|
307
|
+
original_name: "photo.jpg"
|
|
308
|
+
mime_type: "image/jpeg"
|
|
309
|
+
size_bytes: 4521984
|
|
310
|
+
dimensions:
|
|
311
|
+
width: 4032
|
|
312
|
+
height: 3024
|
|
313
|
+
color_space: "sRGB"
|
|
314
|
+
dpi: 72
|
|
315
|
+
has_alpha: false
|
|
316
|
+
exif:
|
|
317
|
+
camera: "iPhone 15 Pro"
|
|
318
|
+
datetime: "2026-03-01T14:30:00Z"
|
|
319
|
+
gps:
|
|
320
|
+
lat: 51.5074
|
|
321
|
+
lon: -0.1278
|
|
322
|
+
copyright: null
|
|
323
|
+
dominant_colors:
|
|
324
|
+
- "#2a5c3f"
|
|
325
|
+
- "#8b6f4e"
|
|
326
|
+
- "#d4c5a9"
|
|
327
|
+
'404':
|
|
328
|
+
description: Upload not found
|
|
329
|
+
content:
|
|
330
|
+
application/json:
|
|
331
|
+
schema:
|
|
332
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
333
|
+
'500':
|
|
334
|
+
description: Internal server error
|
|
335
|
+
content:
|
|
336
|
+
application/json:
|
|
337
|
+
schema:
|
|
338
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
339
|
+
|
|
340
|
+
# ============================================
|
|
341
|
+
# WORKFLOW ENDPOINTS
|
|
342
|
+
# ============================================
|
|
343
|
+
|
|
344
|
+
/api/workflows:
|
|
345
|
+
post:
|
|
346
|
+
summary: Create a workflow
|
|
347
|
+
description: |
|
|
348
|
+
Create a workflow containing one or more jobs, each with one or more operations.
|
|
349
|
+
Jobs reference uploaded files by `file_id`, or depend on other jobs via `source`
|
|
350
|
+
(for single-input downstream jobs) or `inputs` (for multi-input jobs like merge/archive).
|
|
351
|
+
|
|
352
|
+
**Job source types (exactly one required per job):**
|
|
353
|
+
- `file_id`: Reference an uploaded file directly
|
|
354
|
+
- `source`: Reference another job's output (for DAG pipelines)
|
|
355
|
+
- `inputs`: Reference multiple job outputs (for merge/archive)
|
|
356
|
+
|
|
357
|
+
**Workflow edges** define job dependencies as a DAG. Jobs with no dependencies
|
|
358
|
+
start immediately. Jobs with dependencies wait for upstream jobs to complete.
|
|
359
|
+
|
|
360
|
+
**Default operation:** If `operations` is omitted on a single-input job with
|
|
361
|
+
`file_id`, the default is `[{ "type": "compress" }]` with default options for
|
|
362
|
+
the detected MIME type. Multi-input jobs must always specify operations explicitly.
|
|
363
|
+
|
|
364
|
+
**Multi-input constraint:** Multi-input jobs must have exactly one operation,
|
|
365
|
+
which must be a multi-input type (`merge` or `archive`). Chaining after a
|
|
366
|
+
multi-input operation is not valid — create a downstream edge-sourced job instead.
|
|
367
|
+
operationId: createWorkflow
|
|
368
|
+
tags:
|
|
369
|
+
- Workflow
|
|
370
|
+
requestBody:
|
|
371
|
+
required: true
|
|
372
|
+
content:
|
|
373
|
+
application/json:
|
|
374
|
+
schema:
|
|
375
|
+
$ref: '#/components/schemas/WorkflowCreateRequest'
|
|
376
|
+
responses:
|
|
377
|
+
'201':
|
|
378
|
+
description: Workflow created
|
|
379
|
+
content:
|
|
380
|
+
application/json:
|
|
381
|
+
schema:
|
|
382
|
+
$ref: '#/components/schemas/WorkflowCreateSuccessEnvelope'
|
|
383
|
+
examples:
|
|
384
|
+
simple:
|
|
385
|
+
summary: Single file, single operation
|
|
386
|
+
value:
|
|
387
|
+
success: true
|
|
388
|
+
data:
|
|
389
|
+
workflow_id: "019539ac-2222-7000-8000-000000000001"
|
|
390
|
+
status: "pending"
|
|
391
|
+
jobs:
|
|
392
|
+
- ref: "main"
|
|
393
|
+
job_id: "019539ad-3333-7000-8000-000000000001"
|
|
394
|
+
status: "pending"
|
|
395
|
+
depends_on: []
|
|
396
|
+
operations:
|
|
397
|
+
- id: "019539ae-4444-7000-8000-000000000001"
|
|
398
|
+
type: "compress"
|
|
399
|
+
status: "pending"
|
|
400
|
+
batch:
|
|
401
|
+
summary: Batch compress (3 independent files)
|
|
402
|
+
value:
|
|
403
|
+
success: true
|
|
404
|
+
data:
|
|
405
|
+
workflow_id: "019539ac-2222-7000-8000-000000000002"
|
|
406
|
+
status: "pending"
|
|
407
|
+
jobs:
|
|
408
|
+
- ref: "file1"
|
|
409
|
+
job_id: "019539ad-3333-7000-8000-000000000002"
|
|
410
|
+
status: "pending"
|
|
411
|
+
depends_on: []
|
|
412
|
+
operations:
|
|
413
|
+
- id: "019539ae-4444-7000-8000-000000000002"
|
|
414
|
+
type: "compress"
|
|
415
|
+
status: "pending"
|
|
416
|
+
- ref: "file2"
|
|
417
|
+
job_id: "019539ad-3333-7000-8000-000000000003"
|
|
418
|
+
status: "pending"
|
|
419
|
+
depends_on: []
|
|
420
|
+
operations:
|
|
421
|
+
- id: "019539ae-4444-7000-8000-000000000003"
|
|
422
|
+
type: "compress"
|
|
423
|
+
status: "pending"
|
|
424
|
+
- ref: "file3"
|
|
425
|
+
job_id: "019539ad-3333-7000-8000-000000000004"
|
|
426
|
+
status: "pending"
|
|
427
|
+
depends_on: []
|
|
428
|
+
operations:
|
|
429
|
+
- id: "019539ae-4444-7000-8000-000000000004"
|
|
430
|
+
type: "compress"
|
|
431
|
+
status: "pending"
|
|
432
|
+
'400':
|
|
433
|
+
description: Validation failed (malformed request body)
|
|
434
|
+
content:
|
|
435
|
+
application/json:
|
|
436
|
+
schema:
|
|
437
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
438
|
+
'404':
|
|
439
|
+
description: Referenced file_id not found
|
|
440
|
+
content:
|
|
441
|
+
application/json:
|
|
442
|
+
schema:
|
|
443
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
444
|
+
example:
|
|
445
|
+
success: false
|
|
446
|
+
error: "Upload not found: 019539ab-1111-7000-8000-999999999999"
|
|
447
|
+
'422':
|
|
448
|
+
description: |
|
|
449
|
+
Semantically invalid request. Valid JSON but contains errors such as:
|
|
450
|
+
unknown operation type, invalid option combinations, cyclic dependency
|
|
451
|
+
in workflow_edges, multi-input job with multiple operations, or
|
|
452
|
+
source ref pointing to a non-existent job.
|
|
453
|
+
content:
|
|
454
|
+
application/json:
|
|
455
|
+
schema:
|
|
456
|
+
$ref: '#/components/schemas/ValidationErrorEnvelope'
|
|
457
|
+
example:
|
|
458
|
+
success: false
|
|
459
|
+
error: "INVALID_OPTIONS"
|
|
460
|
+
details:
|
|
461
|
+
- operation: "compress"
|
|
462
|
+
option: "quality"
|
|
463
|
+
message: "Must be between 1 and 100"
|
|
464
|
+
- operation: "thumbnail"
|
|
465
|
+
option: "width"
|
|
466
|
+
message: "Required field"
|
|
467
|
+
'429':
|
|
468
|
+
description: Rate limit exceeded
|
|
469
|
+
content:
|
|
470
|
+
application/json:
|
|
471
|
+
schema:
|
|
472
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
473
|
+
'500':
|
|
474
|
+
description: Internal server error
|
|
475
|
+
content:
|
|
476
|
+
application/json:
|
|
477
|
+
schema:
|
|
478
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
479
|
+
|
|
480
|
+
/api/workflows/{id}/status:
|
|
481
|
+
get:
|
|
482
|
+
summary: Get workflow status
|
|
483
|
+
description: |
|
|
484
|
+
Retrieve the current status of a workflow with nested job and operation breakdown.
|
|
485
|
+
Each operation includes its current progress and, when completed, its result
|
|
486
|
+
(download URL, size, metrics).
|
|
487
|
+
operationId: getWorkflowStatus
|
|
488
|
+
tags:
|
|
489
|
+
- Workflow
|
|
490
|
+
parameters:
|
|
491
|
+
- name: id
|
|
492
|
+
in: path
|
|
493
|
+
required: true
|
|
494
|
+
description: Workflow ID (UUID v7)
|
|
495
|
+
schema:
|
|
496
|
+
$ref: '#/components/schemas/UuidV7'
|
|
497
|
+
responses:
|
|
498
|
+
'200':
|
|
499
|
+
description: Workflow status retrieved
|
|
500
|
+
content:
|
|
501
|
+
application/json:
|
|
502
|
+
schema:
|
|
503
|
+
$ref: '#/components/schemas/WorkflowStatusSuccessEnvelope'
|
|
504
|
+
examples:
|
|
505
|
+
in_progress:
|
|
506
|
+
summary: Workflow in progress
|
|
507
|
+
value:
|
|
508
|
+
success: true
|
|
509
|
+
data:
|
|
510
|
+
workflow_id: "019539ac-2222-7000-8000-000000000001"
|
|
511
|
+
status: "in_progress"
|
|
512
|
+
jobs:
|
|
513
|
+
- ref: "main"
|
|
514
|
+
job_id: "019539ad-3333-7000-8000-000000000001"
|
|
515
|
+
status: "in_progress"
|
|
516
|
+
depends_on: []
|
|
517
|
+
operations:
|
|
518
|
+
- id: "019539ae-4444-7000-8000-000000000001"
|
|
519
|
+
type: "compress"
|
|
520
|
+
status: "in_progress"
|
|
521
|
+
progress: 65
|
|
522
|
+
completed:
|
|
523
|
+
summary: Workflow completed
|
|
524
|
+
value:
|
|
525
|
+
success: true
|
|
526
|
+
data:
|
|
527
|
+
workflow_id: "019539ac-2222-7000-8000-000000000001"
|
|
528
|
+
status: "completed"
|
|
529
|
+
jobs:
|
|
530
|
+
- ref: "main"
|
|
531
|
+
job_id: "019539ad-3333-7000-8000-000000000001"
|
|
532
|
+
status: "completed"
|
|
533
|
+
depends_on: []
|
|
534
|
+
operations:
|
|
535
|
+
- id: "019539ae-4444-7000-8000-000000000001"
|
|
536
|
+
type: "compress"
|
|
537
|
+
status: "completed"
|
|
538
|
+
result:
|
|
539
|
+
download_url: "https://cdn.giveitsmaller.com/jobs/019539ad-.../019539ae-.../photo.jpg?token=..."
|
|
540
|
+
size_bytes: 1105920
|
|
541
|
+
metrics:
|
|
542
|
+
compression_ratio: 0.45
|
|
543
|
+
duration_ms: 2340
|
|
544
|
+
'404':
|
|
545
|
+
description: Workflow not found
|
|
546
|
+
content:
|
|
547
|
+
application/json:
|
|
548
|
+
schema:
|
|
549
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
550
|
+
'500':
|
|
551
|
+
description: Internal server error
|
|
552
|
+
content:
|
|
553
|
+
application/json:
|
|
554
|
+
schema:
|
|
555
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
556
|
+
|
|
557
|
+
/api/workflows/{id}/downloads:
|
|
558
|
+
get:
|
|
559
|
+
summary: Get download URLs
|
|
560
|
+
description: |
|
|
561
|
+
Get download URLs for all completed operation outputs in a workflow.
|
|
562
|
+
Each job's operations are listed with their output files and pre-signed
|
|
563
|
+
download URLs.
|
|
564
|
+
operationId: getWorkflowDownloads
|
|
565
|
+
tags:
|
|
566
|
+
- Workflow
|
|
567
|
+
parameters:
|
|
568
|
+
- name: id
|
|
569
|
+
in: path
|
|
570
|
+
required: true
|
|
571
|
+
description: Workflow ID (UUID v7)
|
|
572
|
+
schema:
|
|
573
|
+
$ref: '#/components/schemas/UuidV7'
|
|
574
|
+
responses:
|
|
575
|
+
'200':
|
|
576
|
+
description: Download URLs retrieved
|
|
577
|
+
content:
|
|
578
|
+
application/json:
|
|
579
|
+
schema:
|
|
580
|
+
$ref: '#/components/schemas/WorkflowDownloadSuccessEnvelope'
|
|
581
|
+
example:
|
|
582
|
+
success: true
|
|
583
|
+
data:
|
|
584
|
+
downloads:
|
|
585
|
+
- ref: "main"
|
|
586
|
+
job_id: "019539ad-3333-7000-8000-000000000001"
|
|
587
|
+
files:
|
|
588
|
+
- operation: "compress"
|
|
589
|
+
operation_id: "019539ae-4444-7000-8000-000000000001"
|
|
590
|
+
filename: "photo.jpg"
|
|
591
|
+
size_bytes: 1105920
|
|
592
|
+
download_url: "https://cdn.giveitsmaller.com/jobs/019539ad-.../019539ae-.../photo.jpg?token=..."
|
|
593
|
+
'404':
|
|
594
|
+
description: Workflow not found
|
|
595
|
+
content:
|
|
596
|
+
application/json:
|
|
597
|
+
schema:
|
|
598
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
599
|
+
'500':
|
|
600
|
+
description: Internal server error
|
|
601
|
+
content:
|
|
602
|
+
application/json:
|
|
603
|
+
schema:
|
|
604
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
605
|
+
|
|
606
|
+
/api/workflows/{id}/events:
|
|
607
|
+
get:
|
|
608
|
+
summary: Stream workflow events (SSE)
|
|
609
|
+
description: |
|
|
610
|
+
Server-Sent Events endpoint for real-time workflow progress. The server pushes
|
|
611
|
+
events as workflow, job, and operation statuses change.
|
|
612
|
+
|
|
613
|
+
The connection stays open until the workflow reaches a terminal state
|
|
614
|
+
(`completed`, `failed`, `partially_failed`) or the client disconnects.
|
|
615
|
+
|
|
616
|
+
**Event types:**
|
|
617
|
+
- `operation.progress` — operation progress update (progress percentage)
|
|
618
|
+
- `operation.completed` — individual operation finished successfully
|
|
619
|
+
- `operation.failed` — individual operation failed
|
|
620
|
+
- `job.completed` — all operations in a job finished
|
|
621
|
+
- `job.failed` — job failed
|
|
622
|
+
- `workflow.completed` — all jobs completed successfully
|
|
623
|
+
- `workflow.failed` — all jobs finished, at least one failed
|
|
624
|
+
- `workflow.partially_failed` — some jobs succeeded, some failed
|
|
625
|
+
|
|
626
|
+
**Event data format:**
|
|
627
|
+
```
|
|
628
|
+
event: operation.progress
|
|
629
|
+
data: {"job_ref":"main","operation_id":"op-uuid","type":"compress","progress":65}
|
|
630
|
+
|
|
631
|
+
event: operation.completed
|
|
632
|
+
data: {"job_ref":"main","operation_id":"op-uuid","type":"compress","status":"completed","progress":100}
|
|
633
|
+
|
|
634
|
+
event: workflow.completed
|
|
635
|
+
data: {"workflow_id":"wf-uuid","status":"completed"}
|
|
636
|
+
```
|
|
637
|
+
operationId: streamWorkflowEvents
|
|
638
|
+
tags:
|
|
639
|
+
- Workflow
|
|
640
|
+
parameters:
|
|
641
|
+
- name: id
|
|
642
|
+
in: path
|
|
643
|
+
required: true
|
|
644
|
+
description: Workflow ID (UUID v7)
|
|
645
|
+
schema:
|
|
646
|
+
$ref: '#/components/schemas/UuidV7'
|
|
647
|
+
responses:
|
|
648
|
+
'200':
|
|
649
|
+
description: SSE event stream
|
|
650
|
+
content:
|
|
651
|
+
text/event-stream:
|
|
652
|
+
schema:
|
|
653
|
+
type: string
|
|
654
|
+
description: |
|
|
655
|
+
Server-Sent Events stream. Each event has an `event` field (type)
|
|
656
|
+
and a `data` field (JSON payload). See endpoint description for
|
|
657
|
+
event types and payload shapes.
|
|
658
|
+
'404':
|
|
659
|
+
description: Workflow not found
|
|
660
|
+
content:
|
|
661
|
+
application/json:
|
|
662
|
+
schema:
|
|
663
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
664
|
+
'500':
|
|
665
|
+
description: Internal server error
|
|
666
|
+
content:
|
|
667
|
+
application/json:
|
|
668
|
+
schema:
|
|
669
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
670
|
+
|
|
671
|
+
# ============================================
|
|
672
|
+
# OPERATIONS ENDPOINTS
|
|
673
|
+
# ============================================
|
|
674
|
+
|
|
675
|
+
/api/operations/schema:
|
|
676
|
+
get:
|
|
677
|
+
summary: Get operation schema
|
|
678
|
+
description: |
|
|
679
|
+
Returns the operations schema describing all available operation types,
|
|
680
|
+
their options, constraints, and defaults — grouped by MIME type.
|
|
681
|
+
|
|
682
|
+
**This endpoint does NOT use the standard ResponseEnvelope.** It returns
|
|
683
|
+
the raw schema JSON directly for CDN cacheability (static resource).
|
|
684
|
+
|
|
685
|
+
Supports optional query parameters to filter the schema:
|
|
686
|
+
- `mime_type`: Filter to operations available for a specific MIME type
|
|
687
|
+
- `operation`: Filter to a specific operation type
|
|
688
|
+
|
|
689
|
+
The schema includes conditional validation rules using `depends_on`
|
|
690
|
+
(e.g. `quality` is only valid when `mode: lossy`). Clients should use
|
|
691
|
+
these to build dynamic forms.
|
|
692
|
+
operationId: getOperationsSchema
|
|
693
|
+
tags:
|
|
694
|
+
- Operations
|
|
695
|
+
parameters:
|
|
696
|
+
- name: mime_type
|
|
697
|
+
in: query
|
|
698
|
+
required: false
|
|
699
|
+
description: Filter by MIME type (e.g. `image/jpeg`, `video/mp4`)
|
|
700
|
+
schema:
|
|
701
|
+
type: string
|
|
702
|
+
example: "image/jpeg"
|
|
703
|
+
- name: operation
|
|
704
|
+
in: query
|
|
705
|
+
required: false
|
|
706
|
+
description: Filter by operation type
|
|
707
|
+
schema:
|
|
708
|
+
$ref: '#/components/schemas/OperationType'
|
|
709
|
+
responses:
|
|
710
|
+
'200':
|
|
711
|
+
description: Operation schema (raw JSON, no ResponseEnvelope)
|
|
712
|
+
headers:
|
|
713
|
+
Cache-Control:
|
|
714
|
+
schema:
|
|
715
|
+
type: string
|
|
716
|
+
description: Cache headers for CDN
|
|
717
|
+
example: "public, max-age=3600"
|
|
718
|
+
content:
|
|
719
|
+
application/json:
|
|
720
|
+
schema:
|
|
721
|
+
$ref: '#/components/schemas/OperationsSchemaResponse'
|
|
722
|
+
examples:
|
|
723
|
+
full_schema:
|
|
724
|
+
summary: Full schema (truncated for brevity)
|
|
725
|
+
value:
|
|
726
|
+
schema_version: "1.0.0"
|
|
727
|
+
operations:
|
|
728
|
+
compress:
|
|
729
|
+
description: "Reduce file size while maintaining acceptable quality"
|
|
730
|
+
default: true
|
|
731
|
+
input_model: "single"
|
|
732
|
+
options:
|
|
733
|
+
mode:
|
|
734
|
+
type: "enum"
|
|
735
|
+
values: ["lossy", "lossless", "auto"]
|
|
736
|
+
quality:
|
|
737
|
+
type: "integer"
|
|
738
|
+
min: 1
|
|
739
|
+
max: 100
|
|
740
|
+
depends_on:
|
|
741
|
+
mode: "lossy"
|
|
742
|
+
width:
|
|
743
|
+
type: "integer"
|
|
744
|
+
min: 1
|
|
745
|
+
max: 16384
|
|
746
|
+
height:
|
|
747
|
+
type: "integer"
|
|
748
|
+
min: 1
|
|
749
|
+
max: 16384
|
|
750
|
+
thumbnail:
|
|
751
|
+
description: "Generate a preview image from the source file"
|
|
752
|
+
default: false
|
|
753
|
+
input_model: "single"
|
|
754
|
+
options:
|
|
755
|
+
width:
|
|
756
|
+
type: "integer"
|
|
757
|
+
min: 1
|
|
758
|
+
max: 4096
|
|
759
|
+
required: true
|
|
760
|
+
height:
|
|
761
|
+
type: "integer"
|
|
762
|
+
min: 1
|
|
763
|
+
max: 4096
|
|
764
|
+
required: true
|
|
765
|
+
fit:
|
|
766
|
+
type: "enum"
|
|
767
|
+
values: ["max", "crop", "scale"]
|
|
768
|
+
'500':
|
|
769
|
+
description: Internal server error
|
|
770
|
+
content:
|
|
771
|
+
application/json:
|
|
772
|
+
schema:
|
|
773
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
774
|
+
|
|
775
|
+
/api/operations/{id}/retry:
|
|
776
|
+
post:
|
|
777
|
+
summary: Retry a failed operation
|
|
778
|
+
description: |
|
|
779
|
+
Retry a failed operation without recreating the entire workflow. Creates a new
|
|
780
|
+
operation that replaces the failed one, using the same source file and options.
|
|
781
|
+
|
|
782
|
+
The original failed operation is marked as `retried` and a new operation with
|
|
783
|
+
a new ID is created in `pending` state.
|
|
784
|
+
operationId: retryOperation
|
|
785
|
+
tags:
|
|
786
|
+
- Operations
|
|
787
|
+
parameters:
|
|
788
|
+
- name: id
|
|
789
|
+
in: path
|
|
790
|
+
required: true
|
|
791
|
+
description: Failed operation ID (UUID v7)
|
|
792
|
+
schema:
|
|
793
|
+
$ref: '#/components/schemas/UuidV7'
|
|
794
|
+
responses:
|
|
795
|
+
'202':
|
|
796
|
+
description: Retry accepted, new operation queued
|
|
797
|
+
content:
|
|
798
|
+
application/json:
|
|
799
|
+
schema:
|
|
800
|
+
$ref: '#/components/schemas/RetrySuccessEnvelope'
|
|
801
|
+
example:
|
|
802
|
+
success: true
|
|
803
|
+
data:
|
|
804
|
+
operation_id: "019539af-5555-7000-8000-000000000001"
|
|
805
|
+
original_operation_id: "019539ae-4444-7000-8000-000000000001"
|
|
806
|
+
status: "pending"
|
|
807
|
+
'404':
|
|
808
|
+
description: Operation not found
|
|
809
|
+
content:
|
|
810
|
+
application/json:
|
|
811
|
+
schema:
|
|
812
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
813
|
+
'409':
|
|
814
|
+
description: Operation is not in a failed state, or has already been retried
|
|
815
|
+
content:
|
|
816
|
+
application/json:
|
|
817
|
+
schema:
|
|
818
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
819
|
+
example:
|
|
820
|
+
success: false
|
|
821
|
+
error: "Operation is not in a failed state"
|
|
822
|
+
'500':
|
|
823
|
+
description: Internal server error
|
|
824
|
+
content:
|
|
825
|
+
application/json:
|
|
826
|
+
schema:
|
|
827
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
828
|
+
|
|
829
|
+
# ============================================
|
|
830
|
+
# HEALTH ENDPOINTS
|
|
831
|
+
# ============================================
|
|
832
|
+
|
|
833
|
+
/healthz:
|
|
834
|
+
get:
|
|
835
|
+
summary: Liveness probe
|
|
836
|
+
description: Kubernetes liveness probe endpoint
|
|
837
|
+
operationId: liveness
|
|
838
|
+
tags:
|
|
839
|
+
- Health
|
|
840
|
+
responses:
|
|
841
|
+
'200':
|
|
842
|
+
description: Application is alive
|
|
843
|
+
content:
|
|
844
|
+
application/json:
|
|
845
|
+
schema:
|
|
846
|
+
$ref: '#/components/schemas/LivenessResponse'
|
|
847
|
+
example:
|
|
848
|
+
app: true
|
|
849
|
+
|
|
850
|
+
/readyz:
|
|
851
|
+
get:
|
|
852
|
+
summary: Readiness probe
|
|
853
|
+
description: Kubernetes readiness probe endpoint - checks database and cache connectivity
|
|
854
|
+
operationId: readiness
|
|
855
|
+
tags:
|
|
856
|
+
- Health
|
|
857
|
+
responses:
|
|
858
|
+
'200':
|
|
859
|
+
description: Application is ready to receive traffic
|
|
860
|
+
content:
|
|
861
|
+
application/json:
|
|
862
|
+
schema:
|
|
863
|
+
$ref: '#/components/schemas/ReadinessResponse'
|
|
864
|
+
'503':
|
|
865
|
+
description: Application is not ready
|
|
866
|
+
content:
|
|
867
|
+
application/json:
|
|
868
|
+
schema:
|
|
869
|
+
$ref: '#/components/schemas/ReadinessResponse'
|
|
870
|
+
|
|
871
|
+
# ============================================
|
|
872
|
+
# CONTACT ENDPOINT
|
|
873
|
+
# ============================================
|
|
874
|
+
|
|
875
|
+
/api/contact:
|
|
876
|
+
post:
|
|
877
|
+
summary: Submit contact form
|
|
878
|
+
description: |
|
|
879
|
+
Submit a contact form message. All fields except `name` and `website` are required.
|
|
880
|
+
|
|
881
|
+
The `website` field is a honeypot for bot detection. It is hidden from real users
|
|
882
|
+
via CSS. Legitimate submissions must omit this field or send an empty string.
|
|
883
|
+
The API rejects any request where `website` is non-empty.
|
|
884
|
+
|
|
885
|
+
On success, returns 204 with no body. The API sends the message via its
|
|
886
|
+
configured delivery channel (e.g. email, queue).
|
|
887
|
+
operationId: submitContact
|
|
888
|
+
tags:
|
|
889
|
+
- Contact
|
|
890
|
+
requestBody:
|
|
891
|
+
required: true
|
|
892
|
+
content:
|
|
893
|
+
application/json:
|
|
894
|
+
schema:
|
|
895
|
+
$ref: '#/components/schemas/ContactRequest'
|
|
896
|
+
example:
|
|
897
|
+
name: "Jane Doe"
|
|
898
|
+
email: "jane@example.com"
|
|
899
|
+
subject: "general_enquiry"
|
|
900
|
+
message: "I have a question about supported file formats."
|
|
901
|
+
responses:
|
|
902
|
+
'204':
|
|
903
|
+
description: Contact form submitted successfully. No response body.
|
|
904
|
+
'400':
|
|
905
|
+
description: Validation failed - one or more fields are invalid
|
|
906
|
+
content:
|
|
907
|
+
application/json:
|
|
908
|
+
schema:
|
|
909
|
+
$ref: '#/components/schemas/ContactValidationErrorResponse'
|
|
910
|
+
example:
|
|
911
|
+
errors:
|
|
912
|
+
email:
|
|
913
|
+
- "This value is not a valid email address."
|
|
914
|
+
message:
|
|
915
|
+
- "This field is required."
|
|
916
|
+
'429':
|
|
917
|
+
description: Rate limit exceeded
|
|
918
|
+
content:
|
|
919
|
+
application/json:
|
|
920
|
+
schema:
|
|
921
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
922
|
+
'500':
|
|
923
|
+
description: Internal server error
|
|
924
|
+
content:
|
|
925
|
+
application/json:
|
|
926
|
+
schema:
|
|
927
|
+
$ref: '#/components/schemas/ErrorEnvelope'
|
|
928
|
+
|
|
929
|
+
# ============================================
|
|
930
|
+
# WEBHOOKS (outbound callbacks to consumer URLs)
|
|
931
|
+
# ============================================
|
|
932
|
+
|
|
933
|
+
webhooks:
|
|
934
|
+
workflowCallback:
|
|
935
|
+
post:
|
|
936
|
+
summary: Workflow event callback
|
|
937
|
+
operationId: webhookWorkflowCallback
|
|
938
|
+
description: |
|
|
939
|
+
POSTed to the `callback_url` provided in `WorkflowCreateRequest` when a
|
|
940
|
+
subscribed event occurs. The consumer endpoint must return a 2xx status
|
|
941
|
+
to acknowledge receipt.
|
|
942
|
+
parameters:
|
|
943
|
+
- name: X-GIS-Signature
|
|
944
|
+
in: header
|
|
945
|
+
required: true
|
|
946
|
+
schema:
|
|
947
|
+
type: string
|
|
948
|
+
pattern: '^sha256=[0-9a-f]{64}$'
|
|
949
|
+
description: |
|
|
950
|
+
HMAC-SHA256 signature of the raw request body using the per-workflow
|
|
951
|
+
`webhook_secret` as the key. Format: `sha256=<hex-digest>`.
|
|
952
|
+
|
|
953
|
+
Verification pseudocode:
|
|
954
|
+
```
|
|
955
|
+
expected = "sha256=" + hex(hmac_sha256(webhook_secret, raw_body))
|
|
956
|
+
valid = constant_time_equal(header_value, expected)
|
|
957
|
+
```
|
|
958
|
+
example: "sha256=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
|
|
959
|
+
requestBody:
|
|
960
|
+
required: true
|
|
961
|
+
content:
|
|
962
|
+
application/json:
|
|
963
|
+
schema:
|
|
964
|
+
$ref: '#/components/schemas/WebhookPayload'
|
|
965
|
+
responses:
|
|
966
|
+
'200':
|
|
967
|
+
description: Callback acknowledged
|
|
968
|
+
'202':
|
|
969
|
+
description: Callback accepted for processing
|
|
970
|
+
'204':
|
|
971
|
+
description: Callback acknowledged (no body)
|
|
972
|
+
|
|
973
|
+
components:
|
|
974
|
+
schemas:
|
|
975
|
+
# ============================================
|
|
976
|
+
# SHARED PRIMITIVES
|
|
977
|
+
# ============================================
|
|
978
|
+
|
|
979
|
+
UuidV7:
|
|
980
|
+
type: string
|
|
981
|
+
format: uuid
|
|
982
|
+
pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
|
|
983
|
+
description: UUID v7 format identifier (time-ordered)
|
|
984
|
+
example: "019539ab-1111-7000-8000-000000000001"
|
|
985
|
+
|
|
986
|
+
# ============================================
|
|
987
|
+
# RESPONSE ENVELOPES
|
|
988
|
+
# ============================================
|
|
989
|
+
|
|
990
|
+
ResponseEnvelope:
|
|
991
|
+
type: object
|
|
992
|
+
description: |
|
|
993
|
+
Standard response wrapper. All API responses (except GET /api/operations/schema,
|
|
994
|
+
health probes, and POST /api/contact) use this envelope.
|
|
995
|
+
Success: `{ success: true, data: {...} }`.
|
|
996
|
+
Error: `{ success: false, error: "...", details: [...] }`.
|
|
997
|
+
required:
|
|
998
|
+
- success
|
|
999
|
+
properties:
|
|
1000
|
+
success:
|
|
1001
|
+
type: boolean
|
|
1002
|
+
|
|
1003
|
+
ErrorEnvelope:
|
|
1004
|
+
type: object
|
|
1005
|
+
description: Error response envelope
|
|
1006
|
+
required:
|
|
1007
|
+
- success
|
|
1008
|
+
- error
|
|
1009
|
+
properties:
|
|
1010
|
+
success:
|
|
1011
|
+
type: boolean
|
|
1012
|
+
const: false
|
|
1013
|
+
error:
|
|
1014
|
+
type: string
|
|
1015
|
+
description: Human-readable error message
|
|
1016
|
+
|
|
1017
|
+
ValidationErrorEnvelope:
|
|
1018
|
+
type: object
|
|
1019
|
+
description: |
|
|
1020
|
+
Validation error response with structured details.
|
|
1021
|
+
Used for 422 responses where multiple validation issues are reported.
|
|
1022
|
+
required:
|
|
1023
|
+
- success
|
|
1024
|
+
- error
|
|
1025
|
+
- details
|
|
1026
|
+
properties:
|
|
1027
|
+
success:
|
|
1028
|
+
type: boolean
|
|
1029
|
+
const: false
|
|
1030
|
+
error:
|
|
1031
|
+
type: string
|
|
1032
|
+
description: Error code (e.g. "INVALID_OPTIONS")
|
|
1033
|
+
details:
|
|
1034
|
+
type: array
|
|
1035
|
+
description: List of individual validation errors
|
|
1036
|
+
items:
|
|
1037
|
+
type: object
|
|
1038
|
+
required:
|
|
1039
|
+
- message
|
|
1040
|
+
properties:
|
|
1041
|
+
operation:
|
|
1042
|
+
type: string
|
|
1043
|
+
description: Operation type where the error occurred
|
|
1044
|
+
option:
|
|
1045
|
+
type: string
|
|
1046
|
+
description: Option name that failed validation
|
|
1047
|
+
field:
|
|
1048
|
+
type: string
|
|
1049
|
+
description: Field path for non-operation validation errors
|
|
1050
|
+
message:
|
|
1051
|
+
type: string
|
|
1052
|
+
description: Human-readable error message
|
|
1053
|
+
|
|
1054
|
+
# ============================================
|
|
1055
|
+
# STATUS ENUMS
|
|
1056
|
+
# ============================================
|
|
1057
|
+
|
|
1058
|
+
WorkflowStatus:
|
|
1059
|
+
type: string
|
|
1060
|
+
description: |
|
|
1061
|
+
Workflow lifecycle status:
|
|
1062
|
+
- pending: Created but no jobs have started
|
|
1063
|
+
- in_progress: At least one job is running
|
|
1064
|
+
- completed: All jobs completed successfully
|
|
1065
|
+
- failed: All jobs finished, at least one failed, none succeeded
|
|
1066
|
+
- partially_failed: Some jobs succeeded, some failed
|
|
1067
|
+
enum:
|
|
1068
|
+
- pending
|
|
1069
|
+
- in_progress
|
|
1070
|
+
- completed
|
|
1071
|
+
- failed
|
|
1072
|
+
- partially_failed
|
|
1073
|
+
|
|
1074
|
+
JobStatus:
|
|
1075
|
+
type: string
|
|
1076
|
+
description: |
|
|
1077
|
+
Job lifecycle status:
|
|
1078
|
+
- pending: Created, waiting to be scheduled
|
|
1079
|
+
- waiting: Blocked by upstream job dependencies (workflow_edges)
|
|
1080
|
+
- in_progress: At least one operation is running
|
|
1081
|
+
- completed: All operations completed successfully
|
|
1082
|
+
- failed: Job failed (at least one operation failed)
|
|
1083
|
+
enum:
|
|
1084
|
+
- pending
|
|
1085
|
+
- waiting
|
|
1086
|
+
- in_progress
|
|
1087
|
+
- completed
|
|
1088
|
+
- failed
|
|
1089
|
+
|
|
1090
|
+
OperationStatus:
|
|
1091
|
+
type: string
|
|
1092
|
+
description: |
|
|
1093
|
+
Operation lifecycle status:
|
|
1094
|
+
- pending: Created, waiting to start
|
|
1095
|
+
- in_progress: Currently processing
|
|
1096
|
+
- completed: Finished successfully
|
|
1097
|
+
- failed: Encountered an error
|
|
1098
|
+
- retried: Failed and replaced by a new retry operation
|
|
1099
|
+
enum:
|
|
1100
|
+
- pending
|
|
1101
|
+
- in_progress
|
|
1102
|
+
- completed
|
|
1103
|
+
- failed
|
|
1104
|
+
- retried
|
|
1105
|
+
|
|
1106
|
+
# ============================================
|
|
1107
|
+
# OPERATION & JOB TYPES
|
|
1108
|
+
# ============================================
|
|
1109
|
+
|
|
1110
|
+
OperationType:
|
|
1111
|
+
type: string
|
|
1112
|
+
description: |
|
|
1113
|
+
Available operation types:
|
|
1114
|
+
- compress: Reduce file size (images, audio, video, documents)
|
|
1115
|
+
- thumbnail: Legacy thumbnail value. Generates a preview image
|
|
1116
|
+
for any media type via a single Lambda. Currently the only
|
|
1117
|
+
thumbnail value the compression_api publisher emits; retirement
|
|
1118
|
+
is planned after the publisher adopts the four sub-type values
|
|
1119
|
+
below in a follow-up API PR.
|
|
1120
|
+
- thumbnail_image: Image thumbnail sub-type. Backed by a dedicated
|
|
1121
|
+
Rust image Lambda. Not yet emitted by the publisher.
|
|
1122
|
+
- thumbnail_video: Video thumbnail sub-type. Backed by a dedicated
|
|
1123
|
+
FFmpeg Lambda. Not yet emitted.
|
|
1124
|
+
- thumbnail_document: PDF/EPUB thumbnail sub-type. Backed by a
|
|
1125
|
+
dedicated Ghostscript Lambda. Not yet emitted.
|
|
1126
|
+
- thumbnail_office: Office document (DOCX/XLSX/PPTX/ODT/ODS/ODP)
|
|
1127
|
+
thumbnail sub-type. Backed by a dedicated LibreOffice Lambda.
|
|
1128
|
+
Not yet emitted.
|
|
1129
|
+
- watermark: Apply branding/protection (images only)
|
|
1130
|
+
- merge: Concatenate/combine multiple files into one (images, video, audio, documents/PDF). Multi-input.
|
|
1131
|
+
- archive: Bundle files into ZIP/tar.gz (all types). Multi-input.
|
|
1132
|
+
- convert: Change file format (all types)
|
|
1133
|
+
|
|
1134
|
+
Both the legacy `thumbnail` value and the four sub-type values
|
|
1135
|
+
are valid routing targets today during the thumbnail migration
|
|
1136
|
+
window. See `asyncapi/events.yaml` for the full routing
|
|
1137
|
+
vocabulary and the publisher branching rule.
|
|
1138
|
+
enum:
|
|
1139
|
+
- compress
|
|
1140
|
+
- thumbnail
|
|
1141
|
+
- thumbnail_image
|
|
1142
|
+
- thumbnail_video
|
|
1143
|
+
- thumbnail_document
|
|
1144
|
+
- thumbnail_office
|
|
1145
|
+
- watermark
|
|
1146
|
+
- merge
|
|
1147
|
+
- archive
|
|
1148
|
+
- convert
|
|
1149
|
+
|
|
1150
|
+
OperationInputModel:
|
|
1151
|
+
type: string
|
|
1152
|
+
description: |
|
|
1153
|
+
Whether the operation accepts a single file or multiple files:
|
|
1154
|
+
- single: One input file (compress, thumbnail, thumbnail_image,
|
|
1155
|
+
thumbnail_video, thumbnail_document, thumbnail_office,
|
|
1156
|
+
watermark, convert)
|
|
1157
|
+
- multi: Multiple input files (merge, archive)
|
|
1158
|
+
enum:
|
|
1159
|
+
- single
|
|
1160
|
+
- multi
|
|
1161
|
+
|
|
1162
|
+
JobType:
|
|
1163
|
+
type: string
|
|
1164
|
+
description: |
|
|
1165
|
+
Media type category derived from MIME type. Used as the
|
|
1166
|
+
`job_type` SNS message attribute on the `job-requests` topic —
|
|
1167
|
+
the single filter attribute that routes compression traffic to
|
|
1168
|
+
per-media-type compression queues. Not used by any other SNS
|
|
1169
|
+
topic; non-compression operations are routed by `operation_type`
|
|
1170
|
+
on the separate `operations` topic.
|
|
1171
|
+
|
|
1172
|
+
Derivation:
|
|
1173
|
+
- image/* -> image
|
|
1174
|
+
- video/* -> video
|
|
1175
|
+
- audio/* -> audio
|
|
1176
|
+
- document types -> document (PDF, DOCX, XLSX, PPTX, ODT, ODS, ODP, EPUB)
|
|
1177
|
+
enum:
|
|
1178
|
+
- image
|
|
1179
|
+
- video
|
|
1180
|
+
- audio
|
|
1181
|
+
- document
|
|
1182
|
+
|
|
1183
|
+
# ============================================
|
|
1184
|
+
# UPLOAD SCHEMAS
|
|
1185
|
+
# ============================================
|
|
1186
|
+
|
|
1187
|
+
SingleUploadRequest:
|
|
1188
|
+
type: object
|
|
1189
|
+
required:
|
|
1190
|
+
- file
|
|
1191
|
+
properties:
|
|
1192
|
+
file:
|
|
1193
|
+
type: string
|
|
1194
|
+
format: binary
|
|
1195
|
+
description: The file to upload
|
|
1196
|
+
filename:
|
|
1197
|
+
type: string
|
|
1198
|
+
maxLength: 255
|
|
1199
|
+
pattern: '^[^/\\]+$'
|
|
1200
|
+
description: |
|
|
1201
|
+
Original filename with extension (e.g. "photo.jpg").
|
|
1202
|
+
Optional — browsers include filename automatically in multipart uploads.
|
|
1203
|
+
Must not contain directory separators (/ or \).
|
|
1204
|
+
example: "photo.jpg"
|
|
1205
|
+
|
|
1206
|
+
UploadResponse:
|
|
1207
|
+
type: object
|
|
1208
|
+
description: Upload result data (same shape for single and multipart complete)
|
|
1209
|
+
required:
|
|
1210
|
+
- file_id
|
|
1211
|
+
- original_name
|
|
1212
|
+
- mime_type
|
|
1213
|
+
- size_bytes
|
|
1214
|
+
properties:
|
|
1215
|
+
file_id:
|
|
1216
|
+
$ref: '#/components/schemas/UuidV7'
|
|
1217
|
+
description: Unique file identifier for use in workflow creation
|
|
1218
|
+
original_name:
|
|
1219
|
+
type: string
|
|
1220
|
+
description: Original filename
|
|
1221
|
+
example: "photo.jpg"
|
|
1222
|
+
mime_type:
|
|
1223
|
+
type: string
|
|
1224
|
+
description: Detected MIME type
|
|
1225
|
+
example: "image/jpeg"
|
|
1226
|
+
size_bytes:
|
|
1227
|
+
type: integer
|
|
1228
|
+
format: int64
|
|
1229
|
+
minimum: 1
|
|
1230
|
+
description: File size in bytes
|
|
1231
|
+
example: 2457600
|
|
1232
|
+
|
|
1233
|
+
UploadSuccessEnvelope:
|
|
1234
|
+
type: object
|
|
1235
|
+
required:
|
|
1236
|
+
- success
|
|
1237
|
+
- data
|
|
1238
|
+
properties:
|
|
1239
|
+
success:
|
|
1240
|
+
type: boolean
|
|
1241
|
+
const: true
|
|
1242
|
+
data:
|
|
1243
|
+
$ref: '#/components/schemas/UploadResponse'
|
|
1244
|
+
|
|
1245
|
+
MultipartInitiateRequest:
|
|
1246
|
+
type: object
|
|
1247
|
+
required:
|
|
1248
|
+
- file
|
|
1249
|
+
- filename
|
|
1250
|
+
- total_size
|
|
1251
|
+
properties:
|
|
1252
|
+
file:
|
|
1253
|
+
type: string
|
|
1254
|
+
format: binary
|
|
1255
|
+
description: |
|
|
1256
|
+
First chunk of the file (recommended 8MB).
|
|
1257
|
+
Used for MIME type detection and throughput measurement.
|
|
1258
|
+
Stored as S3 multipart upload part 1.
|
|
1259
|
+
filename:
|
|
1260
|
+
type: string
|
|
1261
|
+
maxLength: 255
|
|
1262
|
+
pattern: '^[^/\\]+$'
|
|
1263
|
+
description: Original filename with extension. Must not contain directory separators.
|
|
1264
|
+
example: "photo.jpg"
|
|
1265
|
+
total_size:
|
|
1266
|
+
type: integer
|
|
1267
|
+
format: int64
|
|
1268
|
+
minimum: 1
|
|
1269
|
+
description: Total file size in bytes (all chunks combined)
|
|
1270
|
+
|
|
1271
|
+
MultipartInitiateResponse:
|
|
1272
|
+
type: object
|
|
1273
|
+
required:
|
|
1274
|
+
- upload_id
|
|
1275
|
+
- mime_type
|
|
1276
|
+
- first_chunk_etag
|
|
1277
|
+
- first_chunk_size_bytes
|
|
1278
|
+
- total_parts
|
|
1279
|
+
- recommended_chunk_size
|
|
1280
|
+
- presigned_urls
|
|
1281
|
+
properties:
|
|
1282
|
+
upload_id:
|
|
1283
|
+
$ref: '#/components/schemas/UuidV7'
|
|
1284
|
+
description: |
|
|
1285
|
+
Multipart upload session identifier. Pass this as `upload_id` in the
|
|
1286
|
+
complete request; after completion, pass the same value as `file_id`
|
|
1287
|
+
when creating workflows via `POST /api/workflows`.
|
|
1288
|
+
mime_type:
|
|
1289
|
+
type: string
|
|
1290
|
+
description: MIME type detected from the first chunk
|
|
1291
|
+
example: "image/jpeg"
|
|
1292
|
+
first_chunk_etag:
|
|
1293
|
+
type: string
|
|
1294
|
+
description: ETag of the first chunk stored as S3 part 1
|
|
1295
|
+
example: '"d8e8fca2dc0f896fd7cb4cb0031ba249"'
|
|
1296
|
+
first_chunk_size_bytes:
|
|
1297
|
+
type: integer
|
|
1298
|
+
description: Size of the first chunk received (for client validation)
|
|
1299
|
+
example: 8388608
|
|
1300
|
+
total_parts:
|
|
1301
|
+
type: integer
|
|
1302
|
+
minimum: 2
|
|
1303
|
+
maximum: 500
|
|
1304
|
+
description: |
|
|
1305
|
+
Total number of parts. The client slices the remaining file into
|
|
1306
|
+
exactly (total_parts - 1) chunks using recommended_chunk_size.
|
|
1307
|
+
The last chunk may be smaller.
|
|
1308
|
+
example: 5
|
|
1309
|
+
recommended_chunk_size:
|
|
1310
|
+
type: integer
|
|
1311
|
+
minimum: 5242880
|
|
1312
|
+
maximum: 104857600
|
|
1313
|
+
description: |
|
|
1314
|
+
Chunk size in bytes for remaining parts. Calculated from first chunk
|
|
1315
|
+
throughput * 5s target, clamped to 5MB-100MB. The last chunk may be
|
|
1316
|
+
smaller than 5MB.
|
|
1317
|
+
example: 10485760
|
|
1318
|
+
presigned_urls:
|
|
1319
|
+
type: array
|
|
1320
|
+
description: |
|
|
1321
|
+
Pre-signed S3 PUT URLs for parts 2 through total_parts.
|
|
1322
|
+
Each URL accepts a PUT request with raw chunk bytes as body.
|
|
1323
|
+
Collect the ETag from each S3 response for the complete request.
|
|
1324
|
+
items:
|
|
1325
|
+
$ref: '#/components/schemas/PresignedUrlPart'
|
|
1326
|
+
|
|
1327
|
+
MultipartInitiateSuccessEnvelope:
|
|
1328
|
+
type: object
|
|
1329
|
+
required:
|
|
1330
|
+
- success
|
|
1331
|
+
- data
|
|
1332
|
+
properties:
|
|
1333
|
+
success:
|
|
1334
|
+
type: boolean
|
|
1335
|
+
const: true
|
|
1336
|
+
data:
|
|
1337
|
+
$ref: '#/components/schemas/MultipartInitiateResponse'
|
|
1338
|
+
|
|
1339
|
+
PresignedUrlPart:
|
|
1340
|
+
type: object
|
|
1341
|
+
required:
|
|
1342
|
+
- part_number
|
|
1343
|
+
- url
|
|
1344
|
+
- expires_at
|
|
1345
|
+
properties:
|
|
1346
|
+
part_number:
|
|
1347
|
+
type: integer
|
|
1348
|
+
minimum: 2
|
|
1349
|
+
description: S3 multipart part number (starts at 2, API handled part 1)
|
|
1350
|
+
url:
|
|
1351
|
+
type: string
|
|
1352
|
+
format: uri
|
|
1353
|
+
description: Pre-signed S3 PUT URL. Send PUT with raw binary chunk as body.
|
|
1354
|
+
expires_at:
|
|
1355
|
+
type: string
|
|
1356
|
+
format: date-time
|
|
1357
|
+
description: |
|
|
1358
|
+
ISO 8601 expiry timestamp. TTL is dynamic: estimated_upload_duration * 2,
|
|
1359
|
+
clamped between 900s and 3600s.
|
|
1360
|
+
|
|
1361
|
+
MultipartCompleteRequest:
|
|
1362
|
+
type: object
|
|
1363
|
+
required:
|
|
1364
|
+
- upload_id
|
|
1365
|
+
- parts
|
|
1366
|
+
properties:
|
|
1367
|
+
upload_id:
|
|
1368
|
+
$ref: '#/components/schemas/UuidV7'
|
|
1369
|
+
description: Multipart upload session identifier from the initiate response
|
|
1370
|
+
parts:
|
|
1371
|
+
type: array
|
|
1372
|
+
description: |
|
|
1373
|
+
ETags for parts 2 through total_parts, collected from S3 PUT responses.
|
|
1374
|
+
Part 1 ETag is already known from the initiate step.
|
|
1375
|
+
minItems: 1
|
|
1376
|
+
items:
|
|
1377
|
+
type: object
|
|
1378
|
+
required:
|
|
1379
|
+
- part_number
|
|
1380
|
+
- etag
|
|
1381
|
+
properties:
|
|
1382
|
+
part_number:
|
|
1383
|
+
type: integer
|
|
1384
|
+
minimum: 2
|
|
1385
|
+
description: S3 part number (must match presigned_urls part_number)
|
|
1386
|
+
etag:
|
|
1387
|
+
type: string
|
|
1388
|
+
description: ETag from S3 PUT response header
|
|
1389
|
+
example: '"d8e8fca2dc0f896fd7cb4cb0031ba249"'
|
|
1390
|
+
|
|
1391
|
+
MultipartCompleteResponse:
|
|
1392
|
+
type: object
|
|
1393
|
+
description: |
|
|
1394
|
+
Result of finalising a multipart upload. Intentionally narrower than
|
|
1395
|
+
`UploadResponse` (single-upload shape) — the server returns only the
|
|
1396
|
+
finalised `upload_id` and a completion status. Clients who need file
|
|
1397
|
+
metadata (original name, MIME type, size) can use the values captured
|
|
1398
|
+
during initiate, or call `GET /api/uploads/{id}/metadata`.
|
|
1399
|
+
required:
|
|
1400
|
+
- upload_id
|
|
1401
|
+
- status
|
|
1402
|
+
properties:
|
|
1403
|
+
upload_id:
|
|
1404
|
+
$ref: '#/components/schemas/UuidV7'
|
|
1405
|
+
description: |
|
|
1406
|
+
Identifier of the finalised upload. Pass this as `file_id` when
|
|
1407
|
+
creating workflows via `POST /api/workflows` — the `file_id`
|
|
1408
|
+
parameter on the workflows endpoint accepts any upload identifier.
|
|
1409
|
+
status:
|
|
1410
|
+
type: string
|
|
1411
|
+
enum:
|
|
1412
|
+
- completed
|
|
1413
|
+
description: Terminal status of the multipart upload session.
|
|
1414
|
+
|
|
1415
|
+
MultipartCompleteSuccessEnvelope:
|
|
1416
|
+
type: object
|
|
1417
|
+
required:
|
|
1418
|
+
- success
|
|
1419
|
+
- data
|
|
1420
|
+
properties:
|
|
1421
|
+
success:
|
|
1422
|
+
type: boolean
|
|
1423
|
+
const: true
|
|
1424
|
+
data:
|
|
1425
|
+
$ref: '#/components/schemas/MultipartCompleteResponse'
|
|
1426
|
+
|
|
1427
|
+
# ============================================
|
|
1428
|
+
# METADATA SCHEMAS
|
|
1429
|
+
# ============================================
|
|
1430
|
+
|
|
1431
|
+
MetadataResponse:
|
|
1432
|
+
type: object
|
|
1433
|
+
description: |
|
|
1434
|
+
File metadata. Fields vary by MIME type. Common fields are always present;
|
|
1435
|
+
type-specific fields (dimensions, exif, duration, etc.) are included when
|
|
1436
|
+
available for the file type.
|
|
1437
|
+
required:
|
|
1438
|
+
- file_id
|
|
1439
|
+
- original_name
|
|
1440
|
+
- mime_type
|
|
1441
|
+
- size_bytes
|
|
1442
|
+
properties:
|
|
1443
|
+
file_id:
|
|
1444
|
+
$ref: '#/components/schemas/UuidV7'
|
|
1445
|
+
original_name:
|
|
1446
|
+
type: string
|
|
1447
|
+
example: "photo.jpg"
|
|
1448
|
+
mime_type:
|
|
1449
|
+
type: string
|
|
1450
|
+
example: "image/jpeg"
|
|
1451
|
+
size_bytes:
|
|
1452
|
+
type: integer
|
|
1453
|
+
format: int64
|
|
1454
|
+
example: 4521984
|
|
1455
|
+
# Image fields
|
|
1456
|
+
dimensions:
|
|
1457
|
+
type: object
|
|
1458
|
+
description: Image/video dimensions (images, video, PDF)
|
|
1459
|
+
properties:
|
|
1460
|
+
width:
|
|
1461
|
+
type: integer
|
|
1462
|
+
example: 4032
|
|
1463
|
+
height:
|
|
1464
|
+
type: integer
|
|
1465
|
+
example: 3024
|
|
1466
|
+
color_space:
|
|
1467
|
+
type: string
|
|
1468
|
+
description: Color space (images)
|
|
1469
|
+
example: "sRGB"
|
|
1470
|
+
dpi:
|
|
1471
|
+
type: integer
|
|
1472
|
+
description: Dots per inch (images, PDF)
|
|
1473
|
+
example: 72
|
|
1474
|
+
has_alpha:
|
|
1475
|
+
type: boolean
|
|
1476
|
+
description: Whether the image has an alpha channel (images)
|
|
1477
|
+
exif:
|
|
1478
|
+
type: object
|
|
1479
|
+
description: EXIF metadata (images)
|
|
1480
|
+
properties:
|
|
1481
|
+
camera:
|
|
1482
|
+
type: string
|
|
1483
|
+
example: "iPhone 15 Pro"
|
|
1484
|
+
datetime:
|
|
1485
|
+
type: string
|
|
1486
|
+
format: date-time
|
|
1487
|
+
gps:
|
|
1488
|
+
type: object
|
|
1489
|
+
properties:
|
|
1490
|
+
lat:
|
|
1491
|
+
type: number
|
|
1492
|
+
format: double
|
|
1493
|
+
lon:
|
|
1494
|
+
type: number
|
|
1495
|
+
format: double
|
|
1496
|
+
copyright:
|
|
1497
|
+
type:
|
|
1498
|
+
- string
|
|
1499
|
+
- "null"
|
|
1500
|
+
dominant_colors:
|
|
1501
|
+
type: array
|
|
1502
|
+
description: Dominant colors as hex strings (images)
|
|
1503
|
+
items:
|
|
1504
|
+
type: string
|
|
1505
|
+
pattern: '^#[0-9a-fA-F]{6}$'
|
|
1506
|
+
example: ["#2a5c3f", "#8b6f4e", "#d4c5a9"]
|
|
1507
|
+
# Video/Audio fields
|
|
1508
|
+
duration:
|
|
1509
|
+
type: number
|
|
1510
|
+
format: double
|
|
1511
|
+
description: Duration in seconds (video, audio)
|
|
1512
|
+
codec:
|
|
1513
|
+
type: string
|
|
1514
|
+
description: Video/audio codec
|
|
1515
|
+
fps:
|
|
1516
|
+
type: number
|
|
1517
|
+
format: double
|
|
1518
|
+
description: Frames per second (video)
|
|
1519
|
+
audio_codec:
|
|
1520
|
+
type: string
|
|
1521
|
+
description: Audio codec (video)
|
|
1522
|
+
bitrate:
|
|
1523
|
+
type: integer
|
|
1524
|
+
description: Bitrate in bps (video, audio)
|
|
1525
|
+
channels:
|
|
1526
|
+
type: integer
|
|
1527
|
+
description: Audio channels (audio)
|
|
1528
|
+
sample_rate:
|
|
1529
|
+
type: integer
|
|
1530
|
+
description: Sample rate in Hz (audio)
|
|
1531
|
+
# Document fields
|
|
1532
|
+
page_count:
|
|
1533
|
+
type: integer
|
|
1534
|
+
description: Number of pages (documents)
|
|
1535
|
+
|
|
1536
|
+
MetadataSuccessEnvelope:
|
|
1537
|
+
type: object
|
|
1538
|
+
required:
|
|
1539
|
+
- success
|
|
1540
|
+
- data
|
|
1541
|
+
properties:
|
|
1542
|
+
success:
|
|
1543
|
+
type: boolean
|
|
1544
|
+
const: true
|
|
1545
|
+
data:
|
|
1546
|
+
$ref: '#/components/schemas/MetadataResponse'
|
|
1547
|
+
|
|
1548
|
+
# ============================================
|
|
1549
|
+
# WORKFLOW REQUEST SCHEMAS
|
|
1550
|
+
# ============================================
|
|
1551
|
+
|
|
1552
|
+
WorkflowCreateRequest:
|
|
1553
|
+
type: object
|
|
1554
|
+
required:
|
|
1555
|
+
- jobs
|
|
1556
|
+
properties:
|
|
1557
|
+
jobs:
|
|
1558
|
+
type: array
|
|
1559
|
+
description: List of jobs in this workflow
|
|
1560
|
+
minItems: 1
|
|
1561
|
+
items:
|
|
1562
|
+
$ref: '#/components/schemas/JobDefinition'
|
|
1563
|
+
workflow_edges:
|
|
1564
|
+
type: array
|
|
1565
|
+
description: |
|
|
1566
|
+
DAG dependency edges between jobs. Each edge defines that a downstream
|
|
1567
|
+
job depends on an upstream job's output. Jobs with no incoming edges
|
|
1568
|
+
start immediately. Jobs with dependencies wait for all upstream jobs.
|
|
1569
|
+
items:
|
|
1570
|
+
$ref: '#/components/schemas/WorkflowEdge'
|
|
1571
|
+
callback_url:
|
|
1572
|
+
type:
|
|
1573
|
+
- string
|
|
1574
|
+
- "null"
|
|
1575
|
+
format: uri
|
|
1576
|
+
pattern: '^https://'
|
|
1577
|
+
description: |
|
|
1578
|
+
Webhook URL (HTTPS only). The API POSTs a `WebhookPayload` JSON body to
|
|
1579
|
+
this URL when matching events occur. The payload includes event type,
|
|
1580
|
+
delivery ID, timestamp, and full workflow state with job results and
|
|
1581
|
+
download URLs. Must use HTTPS to prevent credential leakage and SSRF
|
|
1582
|
+
against internal endpoints.
|
|
1583
|
+
|
|
1584
|
+
**Signature verification:**
|
|
1585
|
+
Each request includes an `X-GIS-Signature` header containing an
|
|
1586
|
+
HMAC-SHA256 hex digest of the raw request body, using the per-workflow
|
|
1587
|
+
`webhook_secret` (returned in the workflow creation response) as the key.
|
|
1588
|
+
Header format: `sha256=<hex(hmac-sha256(webhook_secret, raw_body))>`.
|
|
1589
|
+
Consumers MUST verify the signature before processing the payload.
|
|
1590
|
+
callback_events:
|
|
1591
|
+
type: array
|
|
1592
|
+
description: |
|
|
1593
|
+
Which events trigger the webhook callback. Defaults to terminal events only.
|
|
1594
|
+
items:
|
|
1595
|
+
$ref: '#/components/schemas/CallbackEventType'
|
|
1596
|
+
default:
|
|
1597
|
+
- "workflow.completed"
|
|
1598
|
+
- "workflow.failed"
|
|
1599
|
+
- "workflow.partially_failed"
|
|
1600
|
+
export:
|
|
1601
|
+
$ref: '#/components/schemas/ExportConfig'
|
|
1602
|
+
|
|
1603
|
+
JobDefinition:
|
|
1604
|
+
type: object
|
|
1605
|
+
description: |
|
|
1606
|
+
A job within a workflow. Each job must have exactly one source:
|
|
1607
|
+
- `file_id`: Direct reference to an uploaded file
|
|
1608
|
+
- `source`: Reference another job's output (single-input downstream)
|
|
1609
|
+
- `inputs`: Reference multiple job outputs (multi-input: merge/archive)
|
|
1610
|
+
|
|
1611
|
+
Future versions will add `url` and `import` source types for external
|
|
1612
|
+
file references (V2).
|
|
1613
|
+
|
|
1614
|
+
For single-input jobs with `file_id`, `operations` defaults to
|
|
1615
|
+
`[{ "type": "compress" }]` if omitted. Multi-input jobs must specify
|
|
1616
|
+
operations explicitly.
|
|
1617
|
+
required:
|
|
1618
|
+
- ref
|
|
1619
|
+
properties:
|
|
1620
|
+
ref:
|
|
1621
|
+
type: string
|
|
1622
|
+
description: |
|
|
1623
|
+
Unique reference label within this workflow. Used in workflow_edges,
|
|
1624
|
+
source references, and response payloads to identify jobs.
|
|
1625
|
+
example: "main"
|
|
1626
|
+
file_id:
|
|
1627
|
+
$ref: '#/components/schemas/UuidV7'
|
|
1628
|
+
description: Reference to an uploaded file
|
|
1629
|
+
source:
|
|
1630
|
+
$ref: '#/components/schemas/JobSource'
|
|
1631
|
+
inputs:
|
|
1632
|
+
type: array
|
|
1633
|
+
description: |
|
|
1634
|
+
Multiple input references for multi-input operations (merge, archive).
|
|
1635
|
+
Each input references a specific job's operation output.
|
|
1636
|
+
minItems: 2
|
|
1637
|
+
items:
|
|
1638
|
+
$ref: '#/components/schemas/JobInput'
|
|
1639
|
+
operations:
|
|
1640
|
+
type: array
|
|
1641
|
+
description: |
|
|
1642
|
+
Ordered list of operations to perform. Executed sequentially — each
|
|
1643
|
+
operation consumes the previous operation's output. All intermediate
|
|
1644
|
+
and final outputs are kept.
|
|
1645
|
+
|
|
1646
|
+
Multi-input jobs must have exactly one operation (merge or archive).
|
|
1647
|
+
items:
|
|
1648
|
+
$ref: '#/components/schemas/OperationDefinition'
|
|
1649
|
+
oneOf:
|
|
1650
|
+
- required: [file_id]
|
|
1651
|
+
description: Single-input job referencing an uploaded file
|
|
1652
|
+
- required: [source]
|
|
1653
|
+
description: Single-input downstream job referencing another job's output
|
|
1654
|
+
- required: [inputs]
|
|
1655
|
+
description: Multi-input job (merge/archive)
|
|
1656
|
+
allOf:
|
|
1657
|
+
- if:
|
|
1658
|
+
required: [inputs]
|
|
1659
|
+
then:
|
|
1660
|
+
properties:
|
|
1661
|
+
operations:
|
|
1662
|
+
minItems: 1
|
|
1663
|
+
maxItems: 1
|
|
1664
|
+
items:
|
|
1665
|
+
properties:
|
|
1666
|
+
type:
|
|
1667
|
+
enum: [merge, archive]
|
|
1668
|
+
required: [operations]
|
|
1669
|
+
description: |
|
|
1670
|
+
Multi-input jobs must have exactly one operation, and it must be
|
|
1671
|
+
a multi-input type (merge or archive).
|
|
1672
|
+
|
|
1673
|
+
JobSource:
|
|
1674
|
+
type: object
|
|
1675
|
+
description: |
|
|
1676
|
+
Reference to another job's output for single-input downstream jobs.
|
|
1677
|
+
The input is the referenced job's output, resolved via the incoming
|
|
1678
|
+
workflow edge.
|
|
1679
|
+
required:
|
|
1680
|
+
- ref
|
|
1681
|
+
properties:
|
|
1682
|
+
ref:
|
|
1683
|
+
type: string
|
|
1684
|
+
description: Reference label of the upstream job
|
|
1685
|
+
example: "compress-step"
|
|
1686
|
+
operation:
|
|
1687
|
+
type: string
|
|
1688
|
+
description: |
|
|
1689
|
+
Specific operation output to use from the upstream job.
|
|
1690
|
+
If omitted, uses the last operation's output.
|
|
1691
|
+
|
|
1692
|
+
JobInput:
|
|
1693
|
+
type: object
|
|
1694
|
+
description: |
|
|
1695
|
+
Single input reference for multi-input operations. Each references a
|
|
1696
|
+
specific job and optionally a specific operation output from that job.
|
|
1697
|
+
required:
|
|
1698
|
+
- ref
|
|
1699
|
+
properties:
|
|
1700
|
+
ref:
|
|
1701
|
+
type: string
|
|
1702
|
+
description: Reference label of the upstream job
|
|
1703
|
+
example: "intro"
|
|
1704
|
+
operation:
|
|
1705
|
+
type: string
|
|
1706
|
+
description: |
|
|
1707
|
+
Specific operation output to use. If omitted, uses the last
|
|
1708
|
+
operation's output.
|
|
1709
|
+
per_input_options:
|
|
1710
|
+
type: object
|
|
1711
|
+
description: |
|
|
1712
|
+
Per-input option overrides. For merge operations, individual inputs
|
|
1713
|
+
can override global transition settings for the join point preceding
|
|
1714
|
+
this input. Keys are option names, values are the override values.
|
|
1715
|
+
additionalProperties: true
|
|
1716
|
+
|
|
1717
|
+
OperationDefinition:
|
|
1718
|
+
type: object
|
|
1719
|
+
description: Definition of a single operation within a job
|
|
1720
|
+
required:
|
|
1721
|
+
- type
|
|
1722
|
+
properties:
|
|
1723
|
+
type:
|
|
1724
|
+
$ref: '#/components/schemas/OperationType'
|
|
1725
|
+
options:
|
|
1726
|
+
type: object
|
|
1727
|
+
description: |
|
|
1728
|
+
Operation-specific options. The available options and their validation
|
|
1729
|
+
rules depend on the operation type and the input file's MIME type.
|
|
1730
|
+
See `GET /api/operations/schema` for the full schema.
|
|
1731
|
+
|
|
1732
|
+
Options are validated against the schema using JSON Schema if/then/else
|
|
1733
|
+
rules. For example, `quality` is only valid when `mode: lossy` for
|
|
1734
|
+
compress operations.
|
|
1735
|
+
additionalProperties: true
|
|
1736
|
+
|
|
1737
|
+
WorkflowEdge:
|
|
1738
|
+
type: object
|
|
1739
|
+
description: |
|
|
1740
|
+
Directed edge in the workflow DAG. Defines that the downstream job
|
|
1741
|
+
depends on the upstream job completing successfully.
|
|
1742
|
+
required:
|
|
1743
|
+
- from
|
|
1744
|
+
- to
|
|
1745
|
+
properties:
|
|
1746
|
+
from:
|
|
1747
|
+
type: string
|
|
1748
|
+
description: Reference label of the upstream job
|
|
1749
|
+
example: "compress-intro"
|
|
1750
|
+
to:
|
|
1751
|
+
type: string
|
|
1752
|
+
description: Reference label of the downstream job
|
|
1753
|
+
example: "merge-all"
|
|
1754
|
+
|
|
1755
|
+
# ============================================
|
|
1756
|
+
# WORKFLOW RESPONSE SCHEMAS
|
|
1757
|
+
# ============================================
|
|
1758
|
+
|
|
1759
|
+
WorkflowCreateResponse:
|
|
1760
|
+
type: object
|
|
1761
|
+
required:
|
|
1762
|
+
- workflow_id
|
|
1763
|
+
- status
|
|
1764
|
+
- jobs
|
|
1765
|
+
properties:
|
|
1766
|
+
workflow_id:
|
|
1767
|
+
$ref: '#/components/schemas/UuidV7'
|
|
1768
|
+
status:
|
|
1769
|
+
$ref: '#/components/schemas/WorkflowStatus'
|
|
1770
|
+
jobs:
|
|
1771
|
+
type: array
|
|
1772
|
+
items:
|
|
1773
|
+
$ref: '#/components/schemas/JobResponse'
|
|
1774
|
+
webhook_secret:
|
|
1775
|
+
type:
|
|
1776
|
+
- string
|
|
1777
|
+
- "null"
|
|
1778
|
+
readOnly: true
|
|
1779
|
+
minLength: 64
|
|
1780
|
+
maxLength: 64
|
|
1781
|
+
pattern: '^[0-9a-f]{64}$'
|
|
1782
|
+
description: |
|
|
1783
|
+
HMAC-SHA256 signing key for webhook verification. Present only when
|
|
1784
|
+
`callback_url` was provided in the request. This is the only time the
|
|
1785
|
+
secret is exposed — it does not appear in status queries.
|
|
1786
|
+
|
|
1787
|
+
Use this key to verify the `X-GIS-Signature` header on incoming webhook
|
|
1788
|
+
requests: `sha256=<hex(hmac-sha256(webhook_secret, raw_body))>`.
|
|
1789
|
+
example: "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
|
|
1790
|
+
|
|
1791
|
+
WorkflowCreateSuccessEnvelope:
|
|
1792
|
+
type: object
|
|
1793
|
+
required:
|
|
1794
|
+
- success
|
|
1795
|
+
- data
|
|
1796
|
+
properties:
|
|
1797
|
+
success:
|
|
1798
|
+
type: boolean
|
|
1799
|
+
const: true
|
|
1800
|
+
data:
|
|
1801
|
+
$ref: '#/components/schemas/WorkflowCreateResponse'
|
|
1802
|
+
|
|
1803
|
+
WorkflowStatusResponse:
|
|
1804
|
+
type: object
|
|
1805
|
+
required:
|
|
1806
|
+
- workflow_id
|
|
1807
|
+
- status
|
|
1808
|
+
- jobs
|
|
1809
|
+
properties:
|
|
1810
|
+
workflow_id:
|
|
1811
|
+
$ref: '#/components/schemas/UuidV7'
|
|
1812
|
+
status:
|
|
1813
|
+
$ref: '#/components/schemas/WorkflowStatus'
|
|
1814
|
+
jobs:
|
|
1815
|
+
type: array
|
|
1816
|
+
items:
|
|
1817
|
+
$ref: '#/components/schemas/JobResponse'
|
|
1818
|
+
|
|
1819
|
+
WorkflowStatusSuccessEnvelope:
|
|
1820
|
+
type: object
|
|
1821
|
+
required:
|
|
1822
|
+
- success
|
|
1823
|
+
- data
|
|
1824
|
+
properties:
|
|
1825
|
+
success:
|
|
1826
|
+
type: boolean
|
|
1827
|
+
const: true
|
|
1828
|
+
data:
|
|
1829
|
+
$ref: '#/components/schemas/WorkflowStatusResponse'
|
|
1830
|
+
|
|
1831
|
+
JobResponse:
|
|
1832
|
+
type: object
|
|
1833
|
+
description: Job status within a workflow response
|
|
1834
|
+
required:
|
|
1835
|
+
- ref
|
|
1836
|
+
- job_id
|
|
1837
|
+
- status
|
|
1838
|
+
- depends_on
|
|
1839
|
+
- operations
|
|
1840
|
+
properties:
|
|
1841
|
+
ref:
|
|
1842
|
+
type: string
|
|
1843
|
+
description: Job reference label
|
|
1844
|
+
example: "main"
|
|
1845
|
+
job_id:
|
|
1846
|
+
$ref: '#/components/schemas/UuidV7'
|
|
1847
|
+
status:
|
|
1848
|
+
$ref: '#/components/schemas/JobStatus'
|
|
1849
|
+
depends_on:
|
|
1850
|
+
type: array
|
|
1851
|
+
description: List of upstream job refs this job depends on
|
|
1852
|
+
items:
|
|
1853
|
+
type: string
|
|
1854
|
+
operations:
|
|
1855
|
+
type: array
|
|
1856
|
+
items:
|
|
1857
|
+
$ref: '#/components/schemas/OperationResponse'
|
|
1858
|
+
|
|
1859
|
+
OperationResponse:
|
|
1860
|
+
type: object
|
|
1861
|
+
description: Operation status within a job response
|
|
1862
|
+
required:
|
|
1863
|
+
- id
|
|
1864
|
+
- type
|
|
1865
|
+
- status
|
|
1866
|
+
properties:
|
|
1867
|
+
id:
|
|
1868
|
+
$ref: '#/components/schemas/UuidV7'
|
|
1869
|
+
type:
|
|
1870
|
+
$ref: '#/components/schemas/OperationType'
|
|
1871
|
+
status:
|
|
1872
|
+
$ref: '#/components/schemas/OperationStatus'
|
|
1873
|
+
progress:
|
|
1874
|
+
type: integer
|
|
1875
|
+
minimum: 0
|
|
1876
|
+
maximum: 100
|
|
1877
|
+
description: Progress percentage (0-100). Present when in_progress or completed.
|
|
1878
|
+
result:
|
|
1879
|
+
$ref: '#/components/schemas/OperationResult'
|
|
1880
|
+
|
|
1881
|
+
OperationResult:
|
|
1882
|
+
type: object
|
|
1883
|
+
description: |
|
|
1884
|
+
Result of a completed operation. Present only when operation status is `completed`.
|
|
1885
|
+
required:
|
|
1886
|
+
- download_url
|
|
1887
|
+
- size_bytes
|
|
1888
|
+
properties:
|
|
1889
|
+
download_url:
|
|
1890
|
+
type: string
|
|
1891
|
+
format: uri
|
|
1892
|
+
description: Pre-signed download URL for the operation output
|
|
1893
|
+
size_bytes:
|
|
1894
|
+
type: integer
|
|
1895
|
+
format: int64
|
|
1896
|
+
description: Output file size in bytes
|
|
1897
|
+
export_key:
|
|
1898
|
+
type: string
|
|
1899
|
+
description: Key in the customer's export destination (if export configured)
|
|
1900
|
+
metrics:
|
|
1901
|
+
type: object
|
|
1902
|
+
description: Operation-specific performance metrics
|
|
1903
|
+
properties:
|
|
1904
|
+
compression_ratio:
|
|
1905
|
+
type: number
|
|
1906
|
+
format: double
|
|
1907
|
+
description: Ratio of output size to input size (e.g. 0.45 = 55% reduction)
|
|
1908
|
+
duration_ms:
|
|
1909
|
+
type: integer
|
|
1910
|
+
description: Processing time in milliseconds
|
|
1911
|
+
|
|
1912
|
+
# ============================================
|
|
1913
|
+
# WORKFLOW DOWNLOAD SCHEMAS
|
|
1914
|
+
# ============================================
|
|
1915
|
+
|
|
1916
|
+
WorkflowDownloadResponse:
|
|
1917
|
+
type: object
|
|
1918
|
+
required:
|
|
1919
|
+
- downloads
|
|
1920
|
+
properties:
|
|
1921
|
+
downloads:
|
|
1922
|
+
type: array
|
|
1923
|
+
items:
|
|
1924
|
+
$ref: '#/components/schemas/JobDownload'
|
|
1925
|
+
|
|
1926
|
+
WorkflowDownloadSuccessEnvelope:
|
|
1927
|
+
type: object
|
|
1928
|
+
required:
|
|
1929
|
+
- success
|
|
1930
|
+
- data
|
|
1931
|
+
properties:
|
|
1932
|
+
success:
|
|
1933
|
+
type: boolean
|
|
1934
|
+
const: true
|
|
1935
|
+
data:
|
|
1936
|
+
$ref: '#/components/schemas/WorkflowDownloadResponse'
|
|
1937
|
+
|
|
1938
|
+
JobDownload:
|
|
1939
|
+
type: object
|
|
1940
|
+
required:
|
|
1941
|
+
- ref
|
|
1942
|
+
- job_id
|
|
1943
|
+
- files
|
|
1944
|
+
properties:
|
|
1945
|
+
ref:
|
|
1946
|
+
type: string
|
|
1947
|
+
description: Job reference label
|
|
1948
|
+
job_id:
|
|
1949
|
+
$ref: '#/components/schemas/UuidV7'
|
|
1950
|
+
files:
|
|
1951
|
+
type: array
|
|
1952
|
+
items:
|
|
1953
|
+
$ref: '#/components/schemas/OperationDownload'
|
|
1954
|
+
|
|
1955
|
+
OperationDownload:
|
|
1956
|
+
type: object
|
|
1957
|
+
required:
|
|
1958
|
+
- operation
|
|
1959
|
+
- operation_id
|
|
1960
|
+
- filename
|
|
1961
|
+
- size_bytes
|
|
1962
|
+
- download_url
|
|
1963
|
+
properties:
|
|
1964
|
+
operation:
|
|
1965
|
+
type: string
|
|
1966
|
+
description: Operation type that produced this file
|
|
1967
|
+
operation_id:
|
|
1968
|
+
$ref: '#/components/schemas/UuidV7'
|
|
1969
|
+
filename:
|
|
1970
|
+
type: string
|
|
1971
|
+
description: Output filename
|
|
1972
|
+
example: "photo.jpg"
|
|
1973
|
+
size_bytes:
|
|
1974
|
+
type: integer
|
|
1975
|
+
format: int64
|
|
1976
|
+
description: Output file size in bytes
|
|
1977
|
+
download_url:
|
|
1978
|
+
type: string
|
|
1979
|
+
format: uri
|
|
1980
|
+
description: Pre-signed download URL
|
|
1981
|
+
|
|
1982
|
+
# ============================================
|
|
1983
|
+
# SSE EVENT SCHEMAS
|
|
1984
|
+
# ============================================
|
|
1985
|
+
|
|
1986
|
+
SseEventType:
|
|
1987
|
+
type: string
|
|
1988
|
+
description: |
|
|
1989
|
+
Server-Sent Event types pushed on the /events endpoint:
|
|
1990
|
+
- operation.progress: Progress update for an operation
|
|
1991
|
+
- operation.completed: Operation finished successfully
|
|
1992
|
+
- operation.failed: Operation encountered an error
|
|
1993
|
+
- job.completed: All operations in a job completed
|
|
1994
|
+
- job.failed: Job failed
|
|
1995
|
+
- workflow.completed: All jobs completed successfully
|
|
1996
|
+
- workflow.failed: All jobs finished, at least one failed
|
|
1997
|
+
- workflow.partially_failed: Some succeeded, some failed
|
|
1998
|
+
enum:
|
|
1999
|
+
- operation.progress
|
|
2000
|
+
- operation.completed
|
|
2001
|
+
- operation.failed
|
|
2002
|
+
- job.completed
|
|
2003
|
+
- job.failed
|
|
2004
|
+
- workflow.completed
|
|
2005
|
+
- workflow.failed
|
|
2006
|
+
- workflow.partially_failed
|
|
2007
|
+
|
|
2008
|
+
SseOperationProgressData:
|
|
2009
|
+
type: object
|
|
2010
|
+
description: Payload for `operation.progress` events
|
|
2011
|
+
required:
|
|
2012
|
+
- job_ref
|
|
2013
|
+
- operation_id
|
|
2014
|
+
- type
|
|
2015
|
+
- progress
|
|
2016
|
+
properties:
|
|
2017
|
+
job_ref:
|
|
2018
|
+
type: string
|
|
2019
|
+
operation_id:
|
|
2020
|
+
$ref: '#/components/schemas/UuidV7'
|
|
2021
|
+
type:
|
|
2022
|
+
$ref: '#/components/schemas/OperationType'
|
|
2023
|
+
progress:
|
|
2024
|
+
type: integer
|
|
2025
|
+
minimum: 0
|
|
2026
|
+
maximum: 100
|
|
2027
|
+
|
|
2028
|
+
SseOperationCompletedData:
|
|
2029
|
+
type: object
|
|
2030
|
+
description: Payload for `operation.completed` events
|
|
2031
|
+
required:
|
|
2032
|
+
- job_ref
|
|
2033
|
+
- operation_id
|
|
2034
|
+
- type
|
|
2035
|
+
- status
|
|
2036
|
+
- progress
|
|
2037
|
+
properties:
|
|
2038
|
+
job_ref:
|
|
2039
|
+
type: string
|
|
2040
|
+
operation_id:
|
|
2041
|
+
$ref: '#/components/schemas/UuidV7'
|
|
2042
|
+
type:
|
|
2043
|
+
$ref: '#/components/schemas/OperationType'
|
|
2044
|
+
status:
|
|
2045
|
+
type: string
|
|
2046
|
+
const: "completed"
|
|
2047
|
+
progress:
|
|
2048
|
+
type: integer
|
|
2049
|
+
const: 100
|
|
2050
|
+
result:
|
|
2051
|
+
$ref: '#/components/schemas/OperationResult'
|
|
2052
|
+
|
|
2053
|
+
SseOperationFailedData:
|
|
2054
|
+
type: object
|
|
2055
|
+
description: Payload for `operation.failed` events
|
|
2056
|
+
required:
|
|
2057
|
+
- job_ref
|
|
2058
|
+
- operation_id
|
|
2059
|
+
- type
|
|
2060
|
+
- status
|
|
2061
|
+
- error_code
|
|
2062
|
+
- error_message
|
|
2063
|
+
properties:
|
|
2064
|
+
job_ref:
|
|
2065
|
+
type: string
|
|
2066
|
+
operation_id:
|
|
2067
|
+
$ref: '#/components/schemas/UuidV7'
|
|
2068
|
+
type:
|
|
2069
|
+
$ref: '#/components/schemas/OperationType'
|
|
2070
|
+
status:
|
|
2071
|
+
type: string
|
|
2072
|
+
const: "failed"
|
|
2073
|
+
error_code:
|
|
2074
|
+
type: string
|
|
2075
|
+
error_message:
|
|
2076
|
+
type: string
|
|
2077
|
+
|
|
2078
|
+
SseJobCompletedData:
|
|
2079
|
+
type: object
|
|
2080
|
+
description: Payload for `job.completed` events
|
|
2081
|
+
required:
|
|
2082
|
+
- job_ref
|
|
2083
|
+
- job_id
|
|
2084
|
+
- status
|
|
2085
|
+
properties:
|
|
2086
|
+
job_ref:
|
|
2087
|
+
type: string
|
|
2088
|
+
job_id:
|
|
2089
|
+
$ref: '#/components/schemas/UuidV7'
|
|
2090
|
+
status:
|
|
2091
|
+
type: string
|
|
2092
|
+
const: "completed"
|
|
2093
|
+
|
|
2094
|
+
SseJobFailedData:
|
|
2095
|
+
type: object
|
|
2096
|
+
description: Payload for `job.failed` events
|
|
2097
|
+
required:
|
|
2098
|
+
- job_ref
|
|
2099
|
+
- job_id
|
|
2100
|
+
- status
|
|
2101
|
+
properties:
|
|
2102
|
+
job_ref:
|
|
2103
|
+
type: string
|
|
2104
|
+
job_id:
|
|
2105
|
+
$ref: '#/components/schemas/UuidV7'
|
|
2106
|
+
status:
|
|
2107
|
+
type: string
|
|
2108
|
+
const: "failed"
|
|
2109
|
+
|
|
2110
|
+
SseWorkflowTerminalData:
|
|
2111
|
+
type: object
|
|
2112
|
+
description: |
|
|
2113
|
+
Payload for workflow terminal events
|
|
2114
|
+
(workflow.completed, workflow.failed, workflow.partially_failed)
|
|
2115
|
+
required:
|
|
2116
|
+
- workflow_id
|
|
2117
|
+
- status
|
|
2118
|
+
properties:
|
|
2119
|
+
workflow_id:
|
|
2120
|
+
$ref: '#/components/schemas/UuidV7'
|
|
2121
|
+
status:
|
|
2122
|
+
type: string
|
|
2123
|
+
enum:
|
|
2124
|
+
- completed
|
|
2125
|
+
- failed
|
|
2126
|
+
- partially_failed
|
|
2127
|
+
|
|
2128
|
+
# ============================================
|
|
2129
|
+
# OPERATIONS SCHEMA ENDPOINT RESPONSE
|
|
2130
|
+
# ============================================
|
|
2131
|
+
|
|
2132
|
+
OperationsSchemaResponse:
|
|
2133
|
+
type: object
|
|
2134
|
+
description: |
|
|
2135
|
+
Operations meta-schema. Describes all available operation types, their options,
|
|
2136
|
+
constraints, defaults, and MIME type applicability. Returned raw (no envelope)
|
|
2137
|
+
for CDN cacheability.
|
|
2138
|
+
|
|
2139
|
+
Each operation defines options with types, constraints, and conditional
|
|
2140
|
+
dependencies (via `depends_on`). Clients use this to build dynamic forms
|
|
2141
|
+
and validate options before submission.
|
|
2142
|
+
required:
|
|
2143
|
+
- schema_version
|
|
2144
|
+
- operations
|
|
2145
|
+
properties:
|
|
2146
|
+
schema_version:
|
|
2147
|
+
type: string
|
|
2148
|
+
description: Schema version for cache-busting
|
|
2149
|
+
example: "1.0.0"
|
|
2150
|
+
operations:
|
|
2151
|
+
type: object
|
|
2152
|
+
description: Map of operation type to its schema definition
|
|
2153
|
+
additionalProperties:
|
|
2154
|
+
$ref: '#/components/schemas/OperationSchemaDefinition'
|
|
2155
|
+
|
|
2156
|
+
OperationSchemaDefinition:
|
|
2157
|
+
type: object
|
|
2158
|
+
description: Schema for a single operation type
|
|
2159
|
+
required:
|
|
2160
|
+
- description
|
|
2161
|
+
- input_model
|
|
2162
|
+
- options
|
|
2163
|
+
properties:
|
|
2164
|
+
description:
|
|
2165
|
+
type: string
|
|
2166
|
+
description: Human-readable description of what the operation does
|
|
2167
|
+
default:
|
|
2168
|
+
type: boolean
|
|
2169
|
+
description: Whether this is the default operation when none specified
|
|
2170
|
+
input_model:
|
|
2171
|
+
$ref: '#/components/schemas/OperationInputModel'
|
|
2172
|
+
min_inputs:
|
|
2173
|
+
type: integer
|
|
2174
|
+
description: Minimum number of inputs (multi-input operations only)
|
|
2175
|
+
minimum: 2
|
|
2176
|
+
max_inputs:
|
|
2177
|
+
type: integer
|
|
2178
|
+
description: Maximum number of inputs (multi-input operations only)
|
|
2179
|
+
accepts_mixed_types:
|
|
2180
|
+
type: boolean
|
|
2181
|
+
description: Whether mixed MIME types are allowed (archive only)
|
|
2182
|
+
mime_groups:
|
|
2183
|
+
type: object
|
|
2184
|
+
description: |
|
|
2185
|
+
MIME-type-specific option schemas. When present, options are grouped
|
|
2186
|
+
by MIME category (image, video, audio, document). Each group lists
|
|
2187
|
+
the supported MIME types and group-specific options.
|
|
2188
|
+
additionalProperties:
|
|
2189
|
+
$ref: '#/components/schemas/MimeGroupSchema'
|
|
2190
|
+
options:
|
|
2191
|
+
type: object
|
|
2192
|
+
description: |
|
|
2193
|
+
Global options applicable regardless of MIME type, keyed by option name.
|
|
2194
|
+
For operations with mime_groups, these are the common options.
|
|
2195
|
+
additionalProperties:
|
|
2196
|
+
$ref: '#/components/schemas/OptionSchema'
|
|
2197
|
+
per_input_options:
|
|
2198
|
+
type: object
|
|
2199
|
+
description: |
|
|
2200
|
+
Options that can be overridden per-input for multi-input operations,
|
|
2201
|
+
keyed by option name. For merge: per-join-point transition overrides.
|
|
2202
|
+
additionalProperties:
|
|
2203
|
+
$ref: '#/components/schemas/OptionSchema'
|
|
2204
|
+
|
|
2205
|
+
MimeGroupSchema:
|
|
2206
|
+
type: object
|
|
2207
|
+
description: MIME-group-specific option schema
|
|
2208
|
+
required:
|
|
2209
|
+
- mimes
|
|
2210
|
+
- options
|
|
2211
|
+
properties:
|
|
2212
|
+
mimes:
|
|
2213
|
+
type: array
|
|
2214
|
+
description: List of MIME types in this group
|
|
2215
|
+
items:
|
|
2216
|
+
type: string
|
|
2217
|
+
example: ["image/jpeg", "image/png", "image/webp"]
|
|
2218
|
+
options:
|
|
2219
|
+
type: object
|
|
2220
|
+
description: Options specific to this MIME group, keyed by option name
|
|
2221
|
+
additionalProperties:
|
|
2222
|
+
$ref: '#/components/schemas/OptionSchema'
|
|
2223
|
+
per_input_options:
|
|
2224
|
+
type: object
|
|
2225
|
+
description: Per-input overrides for this MIME group, keyed by option name (multi-input only)
|
|
2226
|
+
additionalProperties:
|
|
2227
|
+
$ref: '#/components/schemas/OptionSchema'
|
|
2228
|
+
|
|
2229
|
+
OptionSchema:
|
|
2230
|
+
type: object
|
|
2231
|
+
description: Schema for a single operation option
|
|
2232
|
+
required:
|
|
2233
|
+
- type
|
|
2234
|
+
properties:
|
|
2235
|
+
type:
|
|
2236
|
+
type: string
|
|
2237
|
+
description: Option value type
|
|
2238
|
+
enum:
|
|
2239
|
+
- integer
|
|
2240
|
+
- float
|
|
2241
|
+
- boolean
|
|
2242
|
+
- enum
|
|
2243
|
+
- string
|
|
2244
|
+
description:
|
|
2245
|
+
type: string
|
|
2246
|
+
description: Human-readable description
|
|
2247
|
+
required:
|
|
2248
|
+
type: boolean
|
|
2249
|
+
description: Whether the option is required
|
|
2250
|
+
default:
|
|
2251
|
+
description: Default value if not specified
|
|
2252
|
+
values:
|
|
2253
|
+
type: array
|
|
2254
|
+
description: Allowed values (for enum type)
|
|
2255
|
+
items: {}
|
|
2256
|
+
value_type:
|
|
2257
|
+
type: string
|
|
2258
|
+
description: |
|
|
2259
|
+
Actual type of enum values when not strings (e.g. "integer" for numeric bitrate enums).
|
|
2260
|
+
Consumers should parse/display values as this type rather than as strings.
|
|
2261
|
+
enum:
|
|
2262
|
+
- integer
|
|
2263
|
+
- float
|
|
2264
|
+
min:
|
|
2265
|
+
type: number
|
|
2266
|
+
description: Minimum value (for integer/float types)
|
|
2267
|
+
max:
|
|
2268
|
+
type: number
|
|
2269
|
+
description: Maximum value (for integer/float types)
|
|
2270
|
+
depends_on:
|
|
2271
|
+
type: object
|
|
2272
|
+
description: |
|
|
2273
|
+
Conditional dependency. This option is only applicable when the condition is met.
|
|
2274
|
+
Simple: `{ "mode": "lossy" }` — option applies when mode equals lossy.
|
|
2275
|
+
Multi-value: `{ "output_format": ["jpeg", "webp"] }` — option applies when output_format is any listed value.
|
|
2276
|
+
Set condition: `{ "width": "set", "height": "set", "logic": "or" }` — option applies when width or height is provided.
|
|
2277
|
+
The "set" sentinel means the option has any value. "logic" can be "and" (default) or "or".
|
|
2278
|
+
additionalProperties: true
|
|
2279
|
+
|
|
2280
|
+
# ============================================
|
|
2281
|
+
# RETRY SCHEMAS
|
|
2282
|
+
# ============================================
|
|
2283
|
+
|
|
2284
|
+
RetryResponse:
|
|
2285
|
+
type: object
|
|
2286
|
+
required:
|
|
2287
|
+
- operation_id
|
|
2288
|
+
- original_operation_id
|
|
2289
|
+
- status
|
|
2290
|
+
properties:
|
|
2291
|
+
operation_id:
|
|
2292
|
+
$ref: '#/components/schemas/UuidV7'
|
|
2293
|
+
description: New operation ID for the retry
|
|
2294
|
+
original_operation_id:
|
|
2295
|
+
$ref: '#/components/schemas/UuidV7'
|
|
2296
|
+
description: ID of the original failed operation
|
|
2297
|
+
status:
|
|
2298
|
+
type: string
|
|
2299
|
+
enum:
|
|
2300
|
+
- pending
|
|
2301
|
+
description: Always "pending" for a new retry
|
|
2302
|
+
|
|
2303
|
+
RetrySuccessEnvelope:
|
|
2304
|
+
type: object
|
|
2305
|
+
required:
|
|
2306
|
+
- success
|
|
2307
|
+
- data
|
|
2308
|
+
properties:
|
|
2309
|
+
success:
|
|
2310
|
+
type: boolean
|
|
2311
|
+
const: true
|
|
2312
|
+
data:
|
|
2313
|
+
$ref: '#/components/schemas/RetryResponse'
|
|
2314
|
+
|
|
2315
|
+
# ============================================
|
|
2316
|
+
# CALLBACK & EXPORT CONFIG
|
|
2317
|
+
# ============================================
|
|
2318
|
+
|
|
2319
|
+
CallbackEventType:
|
|
2320
|
+
type: string
|
|
2321
|
+
description: |
|
|
2322
|
+
Events that can trigger a webhook callback:
|
|
2323
|
+
- workflow.completed: All jobs done successfully
|
|
2324
|
+
- workflow.failed: At least one job failed, none in progress
|
|
2325
|
+
- workflow.partially_failed: Some succeeded, some failed
|
|
2326
|
+
- operation.completed: Individual operation done (opt-in for granular progress)
|
|
2327
|
+
enum:
|
|
2328
|
+
- workflow.completed
|
|
2329
|
+
- workflow.failed
|
|
2330
|
+
- workflow.partially_failed
|
|
2331
|
+
- operation.completed
|
|
2332
|
+
|
|
2333
|
+
WebhookPayload:
|
|
2334
|
+
type: object
|
|
2335
|
+
description: |
|
|
2336
|
+
Payload POSTed to the `callback_url` when a subscribed event occurs.
|
|
2337
|
+
The `workflow` field contains the full current state including all jobs
|
|
2338
|
+
and their operation results, matching the `WorkflowStatusResponse` shape.
|
|
2339
|
+
|
|
2340
|
+
For `operation.completed` events, the `operation` field identifies which
|
|
2341
|
+
specific operation triggered the callback, so consumers do not need to
|
|
2342
|
+
scan the entire workflow to find the change.
|
|
2343
|
+
required:
|
|
2344
|
+
- event_type
|
|
2345
|
+
- delivery_id
|
|
2346
|
+
- timestamp
|
|
2347
|
+
- workflow
|
|
2348
|
+
properties:
|
|
2349
|
+
event_type:
|
|
2350
|
+
$ref: '#/components/schemas/CallbackEventType'
|
|
2351
|
+
delivery_id:
|
|
2352
|
+
$ref: '#/components/schemas/UuidV7'
|
|
2353
|
+
description: |
|
|
2354
|
+
Unique identifier for this event. Stable across retry attempts —
|
|
2355
|
+
the same delivery_id is sent if the API retries a failed delivery.
|
|
2356
|
+
Consumers should use this for idempotency to avoid processing
|
|
2357
|
+
the same event twice.
|
|
2358
|
+
timestamp:
|
|
2359
|
+
type: string
|
|
2360
|
+
format: date-time
|
|
2361
|
+
description: ISO 8601 timestamp of when the event occurred
|
|
2362
|
+
example: "2026-03-13T14:30:00Z"
|
|
2363
|
+
workflow:
|
|
2364
|
+
$ref: '#/components/schemas/WorkflowStatusResponse'
|
|
2365
|
+
operation:
|
|
2366
|
+
$ref: '#/components/schemas/WebhookOperationContext'
|
|
2367
|
+
allOf:
|
|
2368
|
+
- if:
|
|
2369
|
+
properties:
|
|
2370
|
+
event_type:
|
|
2371
|
+
const: operation.completed
|
|
2372
|
+
then:
|
|
2373
|
+
required: [operation]
|
|
2374
|
+
properties:
|
|
2375
|
+
operation:
|
|
2376
|
+
type: object
|
|
2377
|
+
description: operation.completed events must include operation context
|
|
2378
|
+
else:
|
|
2379
|
+
properties:
|
|
2380
|
+
operation:
|
|
2381
|
+
type: "null"
|
|
2382
|
+
description: Workflow-level events have null operation context
|
|
2383
|
+
|
|
2384
|
+
WebhookOperationContext:
|
|
2385
|
+
type:
|
|
2386
|
+
- object
|
|
2387
|
+
- "null"
|
|
2388
|
+
description: |
|
|
2389
|
+
Identifies which operation triggered the callback. Present only for
|
|
2390
|
+
`operation.completed` events; null for workflow-level events.
|
|
2391
|
+
required:
|
|
2392
|
+
- job_ref
|
|
2393
|
+
- operation_id
|
|
2394
|
+
properties:
|
|
2395
|
+
job_ref:
|
|
2396
|
+
type: string
|
|
2397
|
+
description: Reference label of the job containing the operation
|
|
2398
|
+
example: "main"
|
|
2399
|
+
operation_id:
|
|
2400
|
+
$ref: '#/components/schemas/UuidV7'
|
|
2401
|
+
description: ID of the operation that completed
|
|
2402
|
+
|
|
2403
|
+
ExportConfig:
|
|
2404
|
+
type:
|
|
2405
|
+
- object
|
|
2406
|
+
- "null"
|
|
2407
|
+
description: |
|
|
2408
|
+
Export configuration. When set, all operation outputs are copied to the
|
|
2409
|
+
customer's destination in addition to GISL's own S3 storage.
|
|
2410
|
+
Currently supports AWS S3 via cross-account AssumeRole.
|
|
2411
|
+
required:
|
|
2412
|
+
- service
|
|
2413
|
+
- bucket
|
|
2414
|
+
- role_arn
|
|
2415
|
+
properties:
|
|
2416
|
+
service:
|
|
2417
|
+
type: string
|
|
2418
|
+
description: Destination service
|
|
2419
|
+
enum:
|
|
2420
|
+
- s3
|
|
2421
|
+
example: "s3"
|
|
2422
|
+
bucket:
|
|
2423
|
+
type: string
|
|
2424
|
+
description: Destination bucket name
|
|
2425
|
+
example: "customer-output-bucket"
|
|
2426
|
+
key_prefix:
|
|
2427
|
+
type: string
|
|
2428
|
+
description: Key prefix for exported files
|
|
2429
|
+
example: "compressed/"
|
|
2430
|
+
role_arn:
|
|
2431
|
+
type: string
|
|
2432
|
+
description: |
|
|
2433
|
+
IAM role ARN in the customer's AWS account. GISL's Lambda assumes this
|
|
2434
|
+
role to write to the customer's bucket. The customer must configure a
|
|
2435
|
+
trust policy allowing GISL's execution role to assume it.
|
|
2436
|
+
pattern: '^arn:aws:iam::\d{12}:role/.+$'
|
|
2437
|
+
example: "arn:aws:iam::123456789012:role/giveitsmaller-write"
|
|
2438
|
+
|
|
2439
|
+
# ============================================
|
|
2440
|
+
# HEALTH SCHEMAS
|
|
2441
|
+
# ============================================
|
|
2442
|
+
|
|
2443
|
+
LivenessResponse:
|
|
2444
|
+
type: object
|
|
2445
|
+
required:
|
|
2446
|
+
- app
|
|
2447
|
+
properties:
|
|
2448
|
+
app:
|
|
2449
|
+
type: boolean
|
|
2450
|
+
description: Application is running
|
|
2451
|
+
|
|
2452
|
+
ReadinessResponse:
|
|
2453
|
+
type: object
|
|
2454
|
+
properties:
|
|
2455
|
+
database:
|
|
2456
|
+
type: boolean
|
|
2457
|
+
description: Database connection is healthy
|
|
2458
|
+
cache:
|
|
2459
|
+
type: boolean
|
|
2460
|
+
description: Cache connection is healthy
|
|
2461
|
+
|
|
2462
|
+
# ============================================
|
|
2463
|
+
# CONTACT SCHEMAS
|
|
2464
|
+
# ============================================
|
|
2465
|
+
|
|
2466
|
+
ContactSubject:
|
|
2467
|
+
type: string
|
|
2468
|
+
enum:
|
|
2469
|
+
- general_enquiry
|
|
2470
|
+
- bug_report
|
|
2471
|
+
- suggestion
|
|
2472
|
+
- complaint
|
|
2473
|
+
- business_enquiry
|
|
2474
|
+
description: |
|
|
2475
|
+
Subject category:
|
|
2476
|
+
- general_enquiry: General questions
|
|
2477
|
+
- bug_report: Report a bug or issue
|
|
2478
|
+
- suggestion: Feature suggestion or improvement idea
|
|
2479
|
+
- complaint: Complaint about the service
|
|
2480
|
+
- business_enquiry: Business or partnership enquiry
|
|
2481
|
+
|
|
2482
|
+
ContactRequest:
|
|
2483
|
+
type: object
|
|
2484
|
+
required:
|
|
2485
|
+
- email
|
|
2486
|
+
- subject
|
|
2487
|
+
- message
|
|
2488
|
+
properties:
|
|
2489
|
+
name:
|
|
2490
|
+
type: string
|
|
2491
|
+
maxLength: 100
|
|
2492
|
+
description: Sender's name (optional)
|
|
2493
|
+
example: "Jane Doe"
|
|
2494
|
+
email:
|
|
2495
|
+
type: string
|
|
2496
|
+
format: email
|
|
2497
|
+
maxLength: 254
|
|
2498
|
+
description: Sender's email address
|
|
2499
|
+
example: "jane@example.com"
|
|
2500
|
+
subject:
|
|
2501
|
+
$ref: '#/components/schemas/ContactSubject'
|
|
2502
|
+
message:
|
|
2503
|
+
type: string
|
|
2504
|
+
minLength: 1
|
|
2505
|
+
maxLength: 1000
|
|
2506
|
+
description: Message body
|
|
2507
|
+
example: "I have a question about supported file formats."
|
|
2508
|
+
website:
|
|
2509
|
+
type: string
|
|
2510
|
+
maxLength: 255
|
|
2511
|
+
description: |
|
|
2512
|
+
Honeypot field for bot detection. Hidden from real users via CSS.
|
|
2513
|
+
Legitimate submissions must omit this field or send an empty string.
|
|
2514
|
+
The API rejects any request where this field is non-empty.
|
|
2515
|
+
|
|
2516
|
+
ContactValidationErrorResponse:
|
|
2517
|
+
type: object
|
|
2518
|
+
required:
|
|
2519
|
+
- errors
|
|
2520
|
+
properties:
|
|
2521
|
+
errors:
|
|
2522
|
+
type: object
|
|
2523
|
+
description: |
|
|
2524
|
+
Map of field names to arrays of validation error messages.
|
|
2525
|
+
additionalProperties:
|
|
2526
|
+
type: array
|
|
2527
|
+
items:
|
|
2528
|
+
type: string
|
|
2529
|
+
example:
|
|
2530
|
+
email:
|
|
2531
|
+
- "This value is not a valid email address."
|
|
2532
|
+
subject:
|
|
2533
|
+
- "This value is not valid."
|