remora/docs/spec/api/openapi.yaml

761 lines
22 KiB
YAML
Raw Normal View History

2026-07-13 14:59:50 +09:00
openapi: 3.2.0
$self: https://remora.invalid/spec/api/openapi.yaml
jsonSchemaDialect: https://spec.openapis.org/oas/3.2/dialect/2025-09-17
info:
title: Remora API
version: 1.0.0
description: Public job management and service-authenticated worker operations.
servers:
- url: /api
security:
- oidc: []
paths:
/v1/jobs:
post:
operationId: submitJob
parameters:
- $ref: '#/components/parameters/IdempotencyKey'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/JobSubmission'
responses:
'202':
description: Accepted, or the original accepted response for the same key and fingerprint.
headers:
Idempotency-Expires-At:
$ref: '#/components/headers/IdempotencyExpiresAt'
content:
application/json:
schema:
$ref: '#/components/schemas/Job'
'409':
$ref: '#/components/responses/IdempotencyConflict'
default:
$ref: '#/components/responses/Error'
get:
operationId: listJobs
parameters:
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/Limit'
- name: include_deleted
in: query
schema:
type: boolean
default: false
responses:
'200':
description: Authorized keyset page bound to one snapshot and filter.
content:
application/json:
schema:
$ref: '#/components/schemas/JobPage'
default:
$ref: '#/components/responses/Error'
/v1/jobs/{job_id}:
parameters:
- $ref: '#/components/parameters/JobId'
get:
operationId: getJob
responses:
'200':
description: Current authorized job projection.
content:
application/json:
schema:
$ref: '#/components/schemas/Job'
'404':
$ref: '#/components/responses/NotFound'
default:
$ref: '#/components/responses/Error'
/v1/jobs/{job_id}/result:
parameters:
- $ref: '#/components/parameters/JobId'
get:
operationId: getJobResult
responses:
'200':
description: Visible committed manifest-rooted result.
content:
application/json:
schema:
$ref: '#/components/schemas/ResultDescriptor'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/PreconditionFailed'
default:
$ref: '#/components/responses/Error'
/v1/jobs/{job_id}/cancel:
parameters:
- $ref: '#/components/parameters/JobId'
post:
operationId: cancelJob
parameters:
- $ref: '#/components/parameters/IdempotencyKey'
responses:
'202':
description: Cancellation intent accepted or prior response replayed.
content:
application/json:
schema:
$ref: '#/components/schemas/Job'
default:
$ref: '#/components/responses/Error'
/v1/jobs/{job_id}/soft-delete:
parameters:
- $ref: '#/components/parameters/JobId'
post:
operationId: softDeleteJob
parameters:
- $ref: '#/components/parameters/IdempotencyKey'
responses:
'200':
description: Job hidden, or prior response replayed.
content:
application/json:
schema:
$ref: '#/components/schemas/Job'
default:
$ref: '#/components/responses/Error'
/v1/jobs/{job_id}/restore:
parameters:
- $ref: '#/components/parameters/JobId'
post:
operationId: restoreJob
parameters:
- $ref: '#/components/parameters/IdempotencyKey'
responses:
'200':
description: Soft-deleted job restored, or prior response replayed.
content:
application/json:
schema:
$ref: '#/components/schemas/Job'
default:
$ref: '#/components/responses/Error'
/v1/jobs/{job_id}/hard-delete:
parameters:
- $ref: '#/components/parameters/JobId'
post:
operationId: hardDeleteJob
parameters:
- $ref: '#/components/parameters/IdempotencyKey'
responses:
'202':
description: Durable deletion operation created or replayed.
content:
application/json:
schema:
$ref: '#/components/schemas/DeletionOperation'
default:
$ref: '#/components/responses/Error'
/v1/deletions/{operation_id}:
parameters:
- name: operation_id
in: path
required: true
schema:
$ref: '#/components/schemas/OpaqueId'
get:
operationId: getDeletionOperation
responses:
'200':
description: Current deletion operation.
content:
application/json:
schema:
$ref: '#/components/schemas/DeletionOperation'
'404':
$ref: '#/components/responses/NotFound'
default:
$ref: '#/components/responses/Error'
/v1/events:
get:
operationId: connectEvents
parameters:
- name: cursor
in: query
description: Opaque authorization-bound replay cursor from an HTTP snapshot or prior frame.
schema:
type: string
minLength: 16
maxLength: 2048
responses:
'101':
description: WebSocket frames conform to events.schema.json. An absent, invalid, expired, or unauthorized cursor yields resync_required.
default:
$ref: '#/components/responses/Error'
/internal/v1/jobs/{job_id}/claim:
parameters:
- $ref: '#/components/parameters/JobId'
post:
operationId: claimJob
security:
- serviceAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ClaimRequest'
responses:
'200':
description: Allocated authority or replay of the same claim operation.
content:
application/json:
schema:
$ref: '#/components/schemas/AttemptAuthority'
default:
$ref: '#/components/responses/Error'
/internal/v1/attempts/{attempt_id}/renew:
parameters:
- $ref: '#/components/parameters/AttemptId'
post:
operationId: renewAttempt
security:
- serviceAuth: []
requestBody:
required: true
content:
application/json:
schema:
2026-07-13 18:23:03 +09:00
$ref: '#/components/schemas/AttemptRenewRequest'
2026-07-13 14:59:50 +09:00
responses:
'204':
description: Renewed or duplicate operation accepted.
'409':
$ref: '#/components/responses/PreconditionFailed'
default:
$ref: '#/components/responses/Error'
/internal/v1/attempts/{attempt_id}/progress:
parameters:
- $ref: '#/components/parameters/AttemptId'
post:
operationId: reportAttemptProgress
security:
- serviceAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AttemptProgressRequest'
responses:
'204':
description: Current progress accepted or duplicate ignored.
'409':
$ref: '#/components/responses/PreconditionFailed'
default:
$ref: '#/components/responses/Error'
/internal/v1/attempts/{attempt_id}/outcome:
parameters:
- $ref: '#/components/parameters/AttemptId'
post:
operationId: reportAttemptOutcome
security:
- serviceAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AttemptOutcomeRequest'
responses:
'200':
description: Durable outcome projection or replay of the same operation.
content:
application/json:
schema:
$ref: '#/components/schemas/Job'
'409':
$ref: '#/components/responses/PreconditionFailed'
default:
$ref: '#/components/responses/Error'
components:
securitySchemes:
oidc:
type: openIdConnect
openIdConnectUrl: https://identity.invalid/.well-known/openid-configuration
serviceAuth:
type: mutualTLS
parameters:
IdempotencyKey:
name: Idempotency-Key
in: header
required: true
description: Subject- and operation-scoped key retained for at least 24 hours.
schema:
type: string
pattern: '^[A-Za-z0-9._~-]{16,128}$'
Cursor:
name: cursor
in: query
schema:
type: string
minLength: 16
maxLength: 2048
Limit:
name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 200
default: 50
JobId:
name: job_id
in: path
required: true
schema:
$ref: '#/components/schemas/OpaqueId'
AttemptId:
name: attempt_id
in: path
required: true
schema:
$ref: '#/components/schemas/OpaqueId'
headers:
IdempotencyExpiresAt:
description: Earliest time at which the server may forget this key.
schema:
type: string
format: date-time
responses:
Error:
description: Structured error with user-safe detail only.
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ApiError'
NotFound:
description: Missing or unauthorized resource; the response does not distinguish them.
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ApiError'
IdempotencyConflict:
description: The key exists with a different canonical request fingerprint.
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ApiError'
PreconditionFailed:
description: Durable authority, fence, phase, or lifecycle precondition failed.
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ApiError'
schemas:
OpaqueId:
type: string
pattern: '^[A-Za-z0-9][A-Za-z0-9_-]{7,127}$'
Target:
type: object
additionalProperties: false
required: [store, bucket, prefix]
properties:
store:
type: string
minLength: 1
maxLength: 128
bucket:
type: string
pattern: '^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$'
prefix:
type: string
maxLength: 1024
HttpSource:
type: object
additionalProperties: false
required: [kind, url]
properties:
kind:
const: http
url:
type: string
format: uri
maxLength: 8192
credential_ref:
type: string
maxLength: 512
ObjectStoreSource:
type: object
additionalProperties: false
required: [kind, store, bucket, key]
properties:
kind:
const: object_store
store:
type: string
minLength: 1
maxLength: 128
bucket:
type: string
minLength: 3
maxLength: 63
key:
type: string
minLength: 1
maxLength: 4096
version_id:
type: string
maxLength: 1024
credential_ref:
type: string
maxLength: 512
HuggingFaceSource:
type: object
additionalProperties: false
required: [kind, repository_type, repository_id, revision]
properties:
kind:
const: hugging_face
repository_type:
type: string
enum: [model, dataset, space]
repository_id:
type: string
pattern: '^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$'
revision:
type: string
minLength: 1
maxLength: 256
include:
type: array
maxItems: 1024
uniqueItems: true
items:
type: string
minLength: 1
maxLength: 1024
credential_ref:
type: string
maxLength: 512
Source:
oneOf:
- $ref: '#/components/schemas/HttpSource'
- $ref: '#/components/schemas/ObjectStoreSource'
- $ref: '#/components/schemas/HuggingFaceSource'
discriminator:
propertyName: kind
mapping:
http: '#/components/schemas/HttpSource'
object_store: '#/components/schemas/ObjectStoreSource'
hugging_face: '#/components/schemas/HuggingFaceSource'
JobSubmission:
type: object
additionalProperties: false
required: [source, target]
properties:
source:
$ref: '#/components/schemas/Source'
target:
$ref: '#/components/schemas/Target'
label:
type: string
minLength: 1
maxLength: 200
JobPhase:
type: string
enum: [Queued, Running, RetryWait, Cancelling, Succeeded, Failed, Cancelled]
JobLifecycle:
type: string
enum: [visible, soft_deleted, hard_deleting, hard_deleted]
AttemptAuthority:
type: object
additionalProperties: false
required: [attempt_id, fence, worker_incarnation]
properties:
attempt_id:
$ref: '#/components/schemas/OpaqueId'
fence:
type: integer
minimum: 1
worker_incarnation:
$ref: '#/components/schemas/OpaqueId'
Progress:
type: object
additionalProperties: false
required: [attempt_id, fence, completed_bytes, completed_members, observed_at]
properties:
attempt_id:
$ref: '#/components/schemas/OpaqueId'
fence:
type: integer
minimum: 1
completed_bytes:
type: integer
minimum: 0
total_bytes:
type: [integer, 'null']
minimum: 0
completed_members:
type: integer
minimum: 0
total_members:
type: [integer, 'null']
minimum: 0
observed_at:
type: string
format: date-time
CheckpointProjection:
type: object
additionalProperties: false
required: [reusable, validated_bytes, validated_members]
properties:
reusable:
type: boolean
validated_bytes:
type: integer
minimum: 0
validated_members:
type: integer
minimum: 0
JobFailure:
2026-07-13 17:23:32 +09:00
oneOf:
- type: object
additionalProperties: false
required: [class, retryable, message]
properties:
class:
type: string
enum: [validation, authorization, source_not_found, source_changed, cancelled]
retryable:
const: false
message:
type: string
maxLength: 500
safe_details:
type: object
additionalProperties:
type: [string, integer, boolean, 'null']
- type: object
additionalProperties: false
required: [class, retryable, message]
properties:
class:
type: string
enum: [transient_source, transient_target, integrity, capacity, internal, lost_attempt]
retryable:
const: true
message:
type: string
maxLength: 500
safe_details:
type: object
additionalProperties:
type: [string, integer, boolean, 'null']
AttemptRuntimeFailure:
2026-07-13 14:59:50 +09:00
type: object
additionalProperties: false
2026-07-13 17:23:32 +09:00
required: [class, message]
2026-07-13 14:59:50 +09:00
properties:
class:
type: string
2026-07-13 17:23:32 +09:00
enum: [authorization, source_not_found, source_changed, transient_source, transient_target, integrity, capacity, internal]
2026-07-13 14:59:50 +09:00
message:
type: string
maxLength: 500
safe_details:
type: object
additionalProperties:
type: [string, integer, boolean, 'null']
ResultDescriptor:
type: object
additionalProperties: false
required: [kind, manifest, member_count, total_bytes]
properties:
kind:
const: manifest
manifest:
$ref: '#/components/schemas/ObjectIdentity'
member_count:
type: integer
minimum: 1
total_bytes:
type: integer
minimum: 0
ObjectIdentity:
type: object
additionalProperties: false
required: [store, bucket, key]
properties:
store:
type: string
bucket:
type: string
key:
type: string
version_id:
type: string
checksum:
type: string
Job:
type: object
additionalProperties: false
required: [id, owner, revision, phase, lifecycle, source, target, authoritative_attempt, progress, checkpoint, result, failure, created_at, updated_at, event_cursor]
properties:
id:
$ref: '#/components/schemas/OpaqueId'
owner:
$ref: '#/components/schemas/OpaqueId'
revision:
type: integer
minimum: 1
phase:
$ref: '#/components/schemas/JobPhase'
lifecycle:
$ref: '#/components/schemas/JobLifecycle'
source:
$ref: '#/components/schemas/Source'
target:
$ref: '#/components/schemas/Target'
authoritative_attempt:
oneOf:
- $ref: '#/components/schemas/AttemptAuthority'
- type: 'null'
progress:
oneOf:
- $ref: '#/components/schemas/Progress'
- type: 'null'
checkpoint:
oneOf:
- $ref: '#/components/schemas/CheckpointProjection'
- type: 'null'
result:
oneOf:
- $ref: '#/components/schemas/ResultDescriptor'
- type: 'null'
failure:
oneOf:
- $ref: '#/components/schemas/JobFailure'
- type: 'null'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
event_cursor:
type: string
minLength: 16
JobPage:
type: object
additionalProperties: false
required: [items, next_cursor, event_cursor]
properties:
items:
type: array
items:
$ref: '#/components/schemas/Job'
next_cursor:
type: [string, 'null']
event_cursor:
type: string
minLength: 16
DeletionOperation:
type: object
additionalProperties: false
required: [id, job_id, state, created_at, updated_at]
properties:
id:
$ref: '#/components/schemas/OpaqueId'
job_id:
$ref: '#/components/schemas/OpaqueId'
state:
type: string
enum: [queued, running, retry_wait, completed]
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
ApiError:
type: object
additionalProperties: false
required: [code, message, request_id]
properties:
code:
type: string
enum: [invalid_request, unauthenticated, forbidden, not_found, idempotency_conflict, precondition_failed, cursor_invalid, cursor_expired, rate_limited, unavailable, internal]
message:
type: string
maxLength: 500
request_id:
$ref: '#/components/schemas/OpaqueId'
fields:
type: array
maxItems: 100
items:
type: object
additionalProperties: false
required: [pointer, code]
properties:
pointer:
type: string
maxLength: 500
code:
type: string
maxLength: 100
ClaimRequest:
type: object
additionalProperties: false
required: [operation_id, worker_incarnation]
properties:
operation_id:
$ref: '#/components/schemas/OpaqueId'
worker_incarnation:
$ref: '#/components/schemas/OpaqueId'
AttemptMutation:
type: object
required: [operation_id, fence, worker_incarnation]
properties:
operation_id:
$ref: '#/components/schemas/OpaqueId'
fence:
type: integer
minimum: 1
worker_incarnation:
$ref: '#/components/schemas/OpaqueId'
2026-07-13 18:23:03 +09:00
AttemptRenewRequest:
unevaluatedProperties: false
allOf:
- $ref: '#/components/schemas/AttemptMutation'
2026-07-13 14:59:50 +09:00
AttemptProgressRequest:
unevaluatedProperties: false
allOf:
- $ref: '#/components/schemas/AttemptMutation'
- type: object
required: [progress]
properties:
progress:
$ref: '#/components/schemas/Progress'
AttemptOutcomeRequest:
unevaluatedProperties: false
allOf:
- $ref: '#/components/schemas/AttemptMutation'
- type: object
required: [outcome]
properties:
outcome:
oneOf:
- type: object
additionalProperties: false
required: [kind, result]
properties:
kind:
const: success
result:
$ref: '#/components/schemas/ResultDescriptor'
- type: object
additionalProperties: false
required: [kind, failure]
properties:
kind:
const: failure
failure:
2026-07-13 17:23:32 +09:00
$ref: '#/components/schemas/AttemptRuntimeFailure'