-
Notifications
You must be signed in to change notification settings - Fork 79
Expand file tree
/
Copy pathopenapi-ci-api.yaml
More file actions
3983 lines (3835 loc) · 121 KB
/
Copy pathopenapi-ci-api.yaml
File metadata and controls
3983 lines (3835 loc) · 121 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
openapi: 3.0.3
info:
title: CCExtractor CI System API
version: 1.2.0
description: |
Security-hardened JSON-only REST API for the CCExtractor CI/sample platform.
Designed for AI agents and CI automation. Enforces scoped Bearer token auth,
strict input validation, rate limiting on all routes, and safe defaults
throughout. No browser sessions, no HTML, no implicit permissions.
**Authentication:** All endpoints require bearer token authentication unless
explicitly marked with `security: []` (only /system/health and POST /auth/tokens).
**Rate-limit headers:** Every response includes `X-RateLimit-Limit`,
`X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers. These are modeled
explicitly on the 429 response for brevity; they are present on all responses
regardless of status code.
contact:
name: CCExtractor Development
url: https://github.com/CCExtractor/sample-platform
license:
name: GPL-3.0-only
url: https://www.gnu.org/licenses/gpl-3.0.html
servers:
- url: http://localhost:5000/api/v1
description: Local development server
- url: https://sampleplatform.ccextractor.org/api/v1
description: Production
#
# Global security: all endpoints require auth
# unless explicitly overridden with security: []
#
security:
- bearerAuth: []
tags:
- name: Auth
description: Token issuance and revocation
- name: Runs
description: CI run lifecycle — list, inspect, trigger, and cancel
- name: Samples
description: Media samples and regression test definitions
- name: Results
description: Per-sample output, diffs, and baseline management
- name: Errors and Logs
description: Structured errors and raw log access
- name: System
description: Health, queue, and artifacts
- name: Users
description: Platform accounts and role administration
#
# SECURITY NOTES (implementers must read)
#
# 1. AUTH MODEL
# - All tokens are opaque, server-side. Never expose session cookies via API.
# - The CI worker token (/ci/progress-reporter) is a separate secret and is
# NOT valid for user-facing API endpoints.
# - Token creation is rate-limited to 5 req/15 min per IP to prevent
# credential stuffing.
#
# 2. SCOPE ENFORCEMENT
# - Scope checks happen at the middleware layer before route handlers.
# - x-required-scope on each operation defines the minimum scope needed.
# - Missing scope → 403 Forbidden (not 401, token is valid but insufficient).
#
# 3. INPUT VALIDATION
# - additionalProperties: false on all request bodies (no mass-assignment).
# - Regex patterns on all free-text IDs (commit_sha, sha256, repository).
# - maxLength on every string field. maxItems on every array.
# - Integer IDs have minimum: 1 (no zero or negative IDs).
#
# 4. OUTPUT SAFETY
# - got=null in TestResultFile means match, not missing output.
# The dummy row (-1,-1,-1,'','error') is translated server-side to
# status=missing_output and never surfaced as a real object.
# - test.failed reflects cancellation only; fail_count is computed from
# TestResult rows. Do not expose test.failed directly.
# - Stack traces in infrastructure errors are opt-in (include_stack=false
# by default) to avoid leaking internal paths.
#
# 5. STORAGE
# - Artifacts may exist in local SAMPLE_REPOSITORY, GCS, or both.
# - storage_status=degraded means one backend only; missing means neither.
# - Never return a download_url that has not been verified to exist.
# - Log endpoints return 404 (not a broken download link) when the log
# file is absent from both storage backends.
#
# 6. RATE LIMITING (all routes)
# - Default: 120 req/min per token (reads), 20 req/min per token (writes).
# - Auth endpoint: 5 req/15 min per IP.
# - Every response includes X-RateLimit-Limit, X-RateLimit-Remaining,
# X-RateLimit-Reset headers.
# - 429 response includes Retry-After header (seconds).
#
# 7. IDEMPOTENCY
# - POST /runs/{run_id}/cancel is idempotent; canceling an already-canceled
# run returns 202 with status=accepted and a no-op message.
#
# 8. DIFF ACCESS
# - The diff route is header-gated on the legacy system (not role-gated).
# The API wraps the XHR path and returns structured JSON. No HTML.
#
# 9. STATUS DERIVATION
# - Run status is derived, not stored. TestStatus has only: preparation,
# testing, completed, canceled (canceled covers both canceled and error).
# The API normalizes this to the 7-value enum below.
# - RunSample.status is computed from TestResult + TestResultFile +
# expected exit code + multiple acceptable baselines.
# - fail_count and missing_output_count in RunSummary are mutually
# exclusive. A sample appears in exactly one bucket (missing_output
# is checked first; if the dummy sentinel row is detected the function
# returns immediately without evaluating fail conditions).
#
# 10. REPOSITORY PERMISSIONS
# - POST /runs enforces a repo-aware permission check. Triggering a run
# against the main configured repository (GITHUB_OWNER/GITHUB_REPOSITORY)
# requires the contributor role or above. Any authenticated user with
# runs:write scope may trigger runs against fork repositories. There is
# no global repository allowlist; the elevated-role check applies only
# to the main configured repository.
#
paths:
# AUTH
/auth/tokens:
get:
tags: [Auth]
summary: List API tokens
operationId: listTokens
description: >
Lists tokens for the authenticated user. Non-admin users see only their
own tokens. Admins may append ?all=true to list tokens across the entire
system; non-admin callers sending ?all=true receive 403.
Plaintext token values are never included in list responses.
security:
- bearerAuth: []
x-required-scope: tokens:manage
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- name: all
in: query
schema:
type: boolean
description: >
Admin only. Set to true to list tokens for all users in the system.
Non-admin callers receive 403 if this parameter is present and true.
responses:
"200":
description: Paginated list of tokens (without plaintext secrets).
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/Page"
- type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/ApiTokenItem"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
post:
tags: [Auth]
summary: Create an API token
operationId: createToken
description: >
Rate-limited to 5 requests per 15 minutes per IP. Tokens are opaque
and stored server-side. Scopes are additive; request only what you need.
Tokens expire after expires_in_days (default 7, max 30).
security: []
x-rate-limit: "5/15min per IP"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TokenCreateRequest"
responses:
"201":
description: Token created. Store the token value; it will not be shown again.
content:
application/json:
schema:
$ref: "#/components/schemas/AuthToken"
"400":
$ref: "#/components/responses/BadRequest"
"401":
description: Invalid credentials
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
code: invalid_credentials
message: Email or password is incorrect.
details: {}
"403":
description: >
Authenticated caller tried to create a token with higher scopes
than their current token.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
code: forbidden
message: Cannot create token with scopes you do not possess.
details: {}
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
/auth/tokens/current:
delete:
tags: [Auth]
summary: Revoke the current API token
operationId: revokeCurrentToken
description: >
Immediately invalidates the token used in the Authorization header.
Subsequent requests with the same token will receive 401.
No specific scope is required beyond authentication — any valid token
can self-revoke. This is the preferred way to clean up a token when
you have it in hand but do not know its numeric ID.
security:
- bearerAuth: []
responses:
"204":
description: Token revoked
"401":
$ref: "#/components/responses/Unauthorized"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
/auth/tokens/{token_id}:
delete:
tags: [Auth]
summary: Revoke a specific API token by ID
operationId: revokeToken
description: >
Revokes the token identified by token_id.
Any authenticated caller may revoke their own tokens (no scope required).
Admins may revoke any user's token.
A non-admin attempting to revoke a token they do not own receives 404
to prevent token-ID enumeration.
To revoke the token currently in use without knowing its ID, use
DELETE /auth/tokens/current instead.
security:
- bearerAuth: []
parameters:
- name: token_id
in: path
required: true
schema:
type: integer
minimum: 1
responses:
"204":
description: Token revoked successfully.
"401":
$ref: "#/components/responses/Unauthorized"
"404":
description: >
Token not found. Non-admin users attempting to revoke another user's token receive a uniform 404 response to prevent token-ID enumeration.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
example:
code: not_found
message: Token not found.
details: {}
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
/auth/me:
get:
tags: [Auth]
summary: Identify the account behind the current token
operationId: getCurrentUser
description: >
A token does not carry the account's role, so a client cannot infer
what the caller is allowed to do. Clients gate their interface on
this instead of guessing, and the granted scopes are echoed so a
client can also hide what this token could never call. Requires no
particular scope: it reports on the caller and discloses nothing
another endpoint would withhold.
security:
- bearerAuth: []
responses:
"200":
description: The authenticated account
content:
application/json:
schema:
$ref: "#/components/schemas/CurrentUser"
"401":
$ref: "#/components/responses/Unauthorized"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
# RUNS
/runs:
get:
tags: [Runs]
summary: List CI runs
operationId: listRuns
description: >
The underlying table is capped at the 50 most recent runs
in the current implementation; this endpoint adds full pagination.
Sorted by -created_at by default (newest first).
security:
- bearerAuth: []
x-required-scope: runs:read
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- $ref: "#/components/parameters/RunStatus"
- $ref: "#/components/parameters/Branch"
- $ref: "#/components/parameters/CommitSha"
- $ref: "#/components/parameters/Repository"
- $ref: "#/components/parameters/Platform"
- $ref: "#/components/parameters/CreatedAfter"
- $ref: "#/components/parameters/CreatedBefore"
- name: sort
in: query
schema:
type: string
default: -created_at
enum: [created_at, -created_at, run_id, -run_id]
description: Sort field. Prefix with - for descending order.
responses:
"200":
description: Paginated runs
headers:
X-RateLimit-Limit:
$ref: "#/components/headers/RateLimitLimit"
X-RateLimit-Remaining:
$ref: "#/components/headers/RateLimitRemaining"
X-RateLimit-Reset:
$ref: "#/components/headers/RateLimitReset"
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/Page"
- type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/Run"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
post:
tags: [Runs]
summary: Trigger a new CI run
operationId: createRun
description: >
Requires runs:write scope and contributor role or above.
The regression_test_ids set is validated against active tests only.
If omitted, all active regression tests are used.
security:
- bearerAuth: []
x-required-scope: runs:write
x-required-roles: [admin, tester, contributor]
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RunCreateRequest"
responses:
"202":
description: Run queued. Poll /runs/{run_id}/progress for status.
content:
application/json:
schema:
$ref: "#/components/schemas/Run"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"422":
$ref: "#/components/responses/UnprocessableEntity"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
/runs/{run_id}:
get:
tags: [Runs]
summary: Get a CI run
operationId: getRun
description: >
Returns normalized run status derived from TestProgress rows.
status=canceled covers both explicit cancellation and infrastructure
errors (the underlying model does not distinguish them).
security:
- bearerAuth: []
x-required-scope: runs:read
parameters:
- $ref: "#/components/parameters/RunId"
responses:
"200":
description: Run details
content:
application/json:
schema:
$ref: "#/components/schemas/Run"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
/runs/{run_id}/summary:
get:
tags: [Runs]
summary: Get pass/fail summary for a run
operationId: getRunSummary
description: >
fail_count is computed from TestResult rows, not from test.failed.
test.failed only reflects whether the final progress status is
canceled — it does not reflect regression test outcomes.
Use this endpoint, not test.failed, to triage a run.
security:
- bearerAuth: []
x-required-scope: runs:read
parameters:
- $ref: "#/components/parameters/RunId"
responses:
"200":
description: Run summary
content:
application/json:
schema:
$ref: "#/components/schemas/RunSummary"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
/runs/{run_id}/progress:
get:
tags: [Runs]
summary: Get progress events for a run
operationId: getRunProgress
description: >
Progress events are sourced from TestProgress rows written by the CI
worker via /ci/progress-reporter. Messages are unstructured text.
Structured error types are aspirational until the worker protocol
emits structured JSON.
security:
- bearerAuth: []
x-required-scope: runs:read
parameters:
- $ref: "#/components/parameters/RunId"
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- name: status
in: query
schema:
type: string
enum: [queued, preparation, testing, completed, canceled]
responses:
"200":
description: Paginated progress events
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/Page"
- type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/ProgressEvent"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
/runs/{run_id}/cancel:
post:
tags: [Runs]
summary: Cancel a queued or running CI run
operationId: cancelRun
description: >
Idempotent. Canceling an already-canceled or completed run returns
202 with a no-op message rather than an error.
Requires runs:write scope.
security:
- bearerAuth: []
x-required-scope: runs:write
x-required-roles: [admin, tester, contributor]
parameters:
- $ref: "#/components/parameters/RunId"
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
reason:
type: string
minLength: 5
maxLength: 255
description: >
Reason for cancellation, stored in the audit log.
additionalProperties: false
responses:
"202":
description: Cancellation accepted (or no-op if already terminal)
content:
application/json:
schema:
$ref: "#/components/schemas/RunActionResult"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
/runs/{run_id}/config:
get:
tags: [Runs]
summary: Get run configuration and test matrix
operationId: getRunConfig
description: >
regression_test_ids lists IDs included in this run. When no custom
set was configured, all regression tests are returned.
Implementers must filter by active=true explicitly.
security:
- bearerAuth: []
x-required-scope: runs:read
parameters:
- $ref: "#/components/parameters/RunId"
responses:
"200":
description: Run configuration
content:
application/json:
schema:
$ref: "#/components/schemas/RunConfig"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
# SAMPLES
/runs/{run_id}/samples:
get:
tags: [Samples]
summary: List regression test results in a run
operationId: listRunSamples
description: >
Returns one entry per regression test result, not one per unique media
file. A single media sample may yield multiple entries if it has
multiple regression tests (different command flags).
sample_progress in the legacy JSON endpoint is len(test.results) over
total regression tests; it does not reflect multi-output completeness.
security:
- bearerAuth: []
x-required-scope: runs:read
parameters:
- $ref: "#/components/parameters/RunId"
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- name: status
in: query
schema:
type: string
enum: [pass, fail, skipped, missing_output, running, not_started]
- name: name
in: query
schema:
type: string
maxLength: 100
- name: tag
in: query
schema:
type: string
maxLength: 50
- name: category
in: query
schema:
type: string
maxLength: 50
responses:
"200":
description: Paginated regression test results
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/Page"
- type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/RunSample"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
/runs/{run_id}/samples/{regression_test_id}:
get:
tags: [Samples]
summary: Get full details for a regression test result in a run
operationId: getRunSample
description: >
Returns the result for a specific regression test within a run.
Note: the path parameter is regression_test_id, not a media sample ID.
A single media sample may have multiple regression tests.
security:
- bearerAuth: []
x-required-scope: runs:read
parameters:
- $ref: "#/components/parameters/RunId"
- $ref: "#/components/parameters/RegressionTestId"
responses:
"200":
description: Regression test result details
content:
application/json:
schema:
$ref: "#/components/schemas/RunSample"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
/samples:
get:
tags: [Samples]
summary: List all known media samples
operationId: listSamples
description: >
Returns paginated media sample metadata. Samples are the original
media files uploaded for regression testing.
security:
- bearerAuth: []
x-required-scope: runs:read
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- name: status
in: query
description: >
Derived from linked regression tests. The sample table itself has
no quarantine state; active/inactive reflects whether any active
regression tests reference the sample.
schema:
type: string
enum: [active, inactive]
- name: name
in: query
schema:
type: string
maxLength: 100
- name: tag
in: query
schema:
type: string
maxLength: 50
- name: sha256
in: query
schema:
type: string
pattern: '^[a-fA-F0-9]{64}$'
- name: extension
in: query
schema:
type: string
maxLength: 10
pattern: '^[a-zA-Z0-9]+$'
responses:
"200":
description: Paginated media samples
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/Page"
- type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/Sample"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
/samples/{sample_id}:
get:
tags: [Samples]
summary: Get media sample metadata
operationId: getSample
security:
- bearerAuth: []
x-required-scope: runs:read
parameters:
- $ref: "#/components/parameters/SampleId"
responses:
"200":
description: Media sample metadata
content:
application/json:
schema:
$ref: "#/components/schemas/Sample"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
/samples/{sample_id}/details:
get:
tags: [Samples]
summary: Get the full detail payload for a media sample
operationId: getSampleDetails
description: >
Extends the summary payload with the upload record, any extra files
attached to the sample, and the parsed MediaInfo tree. Media info is
best effort: a sample whose XML is missing or unparseable reports
null rather than failing the response. Unlike the classic page this
never regenerates the XML, because a GET must not write to the
sample repository.
security:
- bearerAuth: []
x-required-scope: runs:read
parameters:
- $ref: "#/components/parameters/SampleId"
responses:
"200":
description: Full media sample detail
content:
application/json:
schema:
$ref: "#/components/schemas/SampleDetails"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
/samples/{sample_id}/history:
get:
tags: [Samples]
summary: Get regression test result history for a sample across runs
operationId: getSampleHistory
description: >
Use failure_signature for flake detection: a stable signature across
multiple runs on different commits indicates a genuine regression,
not infrastructure noise.
security:
- bearerAuth: []
x-required-scope: runs:read
parameters:
- $ref: "#/components/parameters/SampleId"
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- $ref: "#/components/parameters/RunStatus"
- $ref: "#/components/parameters/Branch"
- $ref: "#/components/parameters/Platform"
- $ref: "#/components/parameters/CreatedAfter"
- $ref: "#/components/parameters/CreatedBefore"
responses:
"200":
description: Paginated sample history
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/Page"
- type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/SampleHistoryEntry"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
/regression-tests:
get:
tags: [Samples]
summary: List regression test definitions
operationId: listRegressionTests
description: >
The active filter must be applied explicitly. When no custom set is
defined, all regression tests are returned — including inactive ones.
security:
- bearerAuth: []
x-required-scope: runs:read
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- name: active
in: query
schema:
type: boolean
default: true
- name: category
in: query
schema:
type: string
maxLength: 50
- name: tag
in: query
schema:
type: string
maxLength: 50
- name: sample_id
in: query
schema:
type: integer
minimum: 1
responses:
"200":
description: Paginated regression test definitions
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/Page"
- type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/RegressionTest"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"
post:
tags: [Samples]
summary: Create a regression test definition
operationId: createRegressionTest
description: >
Admin or contributor only. The test is created inactive unless the
body says otherwise, so it cannot join a CI suite before someone has
seen the output it actually produces on a verification run. Every
named category must already exist; an unknown name rejects the whole
request rather than silently dropping it.
security:
- bearerAuth: []
x-required-scope: runs:write
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RegressionTestCreateRequest"
responses:
"201":
description: The created regression test
content:
application/json:
schema:
$ref: "#/components/schemas/RegressionTest"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"429":
$ref: "#/components/responses/RateLimited"
default:
$ref: "#/components/responses/Error"