openapi: 3.1.0
info:
  title: RelayOrb Gateway API
  version: 0.1.2
  description: >-
    Public RelayOrb gateway surface used by the anonymous demo and production deployments. Response examples are based
    on the repository docs and current gateway envelopes.
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
servers:
  - url: http://34.8.48.11
    description: Current anonymous demo endpoint (can change; canonical updates are in docs/DEMO.md).
  - url: https://relayorb.com
    description: RelayOrb documentation and discovery host.
externalDocs:
  description: Canonical API documentation
  url: https://github.com/khalidsaidi/relayorb/blob/main/docs/API.md
paths:
  /health:
    get:
      operationId: getHealth
      summary: Gateway health envelope
      responses:
        '200':
          description: Healthy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
              examples:
                ok:
                  value:
                    requestId: b2551f84-d0da-4f24-b589-d77e06af1ad3
                    traceId: eb7e5654-2adf-4f2b-aa95-4e6b95bf4c58
                    status: ok
                    data:
                      service: relayorb-gateway
                      status: ok
  /v1/invoke:
    post:
      operationId: invokeCapability
      summary: Synchronous capability invocation
      description: Idempotent by requestId. Replays completed requests and can return 202 for in-progress duplicates.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvokeRequest'
            examples:
              ragSearch:
                value:
                  requestId: 8f5b5b6a-8d10-4a45-89b6-89fb67235d50
                  caller:
                    agentId: agent-123
                    role: researcher
                    budgetKey: team-a
                  capability: rag.search@v1
                  payload:
                    query: market risk
                    topK: 3
                  trace:
                    parentSpanId: optional
      responses:
        '200':
          description: Invocation completed or replayed completion
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvokeSuccessResponse'
              examples:
                completed:
                  value:
                    requestId: 8f5b5b6a-8d10-4a45-89b6-89fb67235d50
                    traceId: 2c3fcf67-2fa1-44f1-9ff9-d7ba88ab8dc2
                    status: ok
                    data:
                      results:
                        - id: doc-1
                          text: RelayOrb routes capability invocations through a policy-aware gateway.
                          score: 0.95
                      provider: relayorb-rag
                    meta:
                      routedTo: http://worker:8090
                      latencyMs: 42
                      retries: 0
                      traceId: 2c3fcf67-2fa1-44f1-9ff9-d7ba88ab8dc2
                replayed:
                  value:
                    requestId: 8f5b5b6a-8d10-4a45-89b6-89fb67235d50
                    traceId: f86533c0-b858-4f6d-bf7f-8f4121655dcb
                    status: ok
                    data:
                      results:
                        - id: doc-1
                          text: RelayOrb routes capability invocations through a policy-aware gateway.
                          score: 0.95
                      provider: relayorb-rag
                    meta:
                      routedTo: http://worker:8090
                      latencyMs: 40
                      retries: 0
                      traceId: 2c3fcf67-2fa1-44f1-9ff9-d7ba88ab8dc2
                      replayed: true
        '202':
          description: Duplicate request accepted while original invocation is still in progress
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvokeInProgressResponse'
              example:
                requestId: 8f5b5b6a-8d10-4a45-89b6-89fb67235d50
                traceId: new-request-trace
                status: ok
                data:
                  state: in_progress
                meta:
                  replayed: true
                  retryAfterMs: 500
                  traceId: original-request-trace
        '400':
          description: Invalid JSON or schema validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                requestId: demo-bad-request
                traceId: 14a846e2-3576-40f7-9f66-41d8de62b510
                status: error
                error:
                  code: SCHEMA_VALIDATION_FAILED
                  message: invalid invoke request
                  details:
                    errors:
                      - '$.payload.query: expected string'
        '429':
          description: Rate limit or budget guardrail exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                requestId: demo-rate-limit
                traceId: 6f241b40-c40c-4f7d-a6c6-ea5a7305f0ff
                status: error
                error:
                  code: BUDGET_EXCEEDED
                  message: demo rate limit exceeded
                  details:
                    retryAfterSeconds: 1
        '503':
          description: No healthy provider available
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                requestId: demo-unavailable
                traceId: 128f877f-0d6f-4f2c-b9f3-af1a4f37dce4
                status: error
                error:
                  code: NO_HEALTHY_PROVIDERS
                  message: no healthy providers for 'rag.search@v1'
                  details:
                    capability: rag.search@v1
  /v1/submit:
    post:
      operationId: submitCapabilityJob
      summary: Asynchronous capability submission
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubmitRequest'
      responses:
        '200':
          description: Idempotent replay of existing async job
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubmitResponseEnvelope'
              example:
                requestId: 8f5b5b6a-8d10-4a45-89b6-89fb67235d50
                traceId: 2c3fcf67-2fa1-44f1-9ff9-d7ba88ab8dc2
                status: ok
                data:
                  jobId: b10d5a2c-7f98-4117-9ac8-18f5f3ce29d4
                  requestId: 8f5b5b6a-8d10-4a45-89b6-89fb67235d50
                  state: queued
                  statusUrl: /v1/jobs/b10d5a2c-7f98-4117-9ac8-18f5f3ce29d4
                  attempts: 0
                  maxAttempts: 3
                meta:
                  traceId: 2c3fcf67-2fa1-44f1-9ff9-d7ba88ab8dc2
                  replayed: true
        '202':
          description: Queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubmitResponseEnvelope'
              example:
                requestId: 8f5b5b6a-8d10-4a45-89b6-89fb67235d50
                traceId: 2c3fcf67-2fa1-44f1-9ff9-d7ba88ab8dc2
                status: ok
                data:
                  jobId: b10d5a2c-7f98-4117-9ac8-18f5f3ce29d4
                  requestId: 8f5b5b6a-8d10-4a45-89b6-89fb67235d50
                  state: queued
                  statusUrl: /v1/jobs/b10d5a2c-7f98-4117-9ac8-18f5f3ce29d4
                  attempts: 0
                  maxAttempts: 3
                meta:
                  traceId: 2c3fcf67-2fa1-44f1-9ff9-d7ba88ab8dc2
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /v1/jobs/{jobId}:
    get:
      operationId: getJobStatus
      summary: Get asynchronous job status
      parameters:
        - name: jobId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Job status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobStatusEnvelope'
              example:
                requestId: b10d5a2c-7f98-4117-9ac8-18f5f3ce29d4
                traceId: 2c3fcf67-2fa1-44f1-9ff9-d7ba88ab8dc2
                status: ok
                data:
                  jobId: b10d5a2c-7f98-4117-9ac8-18f5f3ce29d4
                  requestId: 8f5b5b6a-8d10-4a45-89b6-89fb67235d50
                  capabilityId: rag.search@v1
                  callerAgentId: agent-123
                  state: succeeded
                  attempts: 1
                  maxAttempts: 3
                  traceId: 2c3fcf67-2fa1-44f1-9ff9-d7ba88ab8dc2
                  createdAt: 1738880000
                  startedAt: 1738880001
                  finishedAt: 1738880002
                  result:
                    results:
                      - id: doc-1
                        text: RelayOrb routes capability invocations through a policy-aware gateway.
                        score: 0.95
                    provider: relayorb-rag
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                requestId: b10d5a2c-7f98-4117-9ac8-18f5f3ce29d4
                traceId: 2c3fcf67-2fa1-44f1-9ff9-d7ba88ab8dc2
                status: error
                error:
                  code: FORBIDDEN
                  message: caller is not authorized to read this job
                  details: {}
  /v1/replay/{requestId}:
    get:
      operationId: getReplayRecord
      summary: Get canonical invocation replay artifact
      parameters:
        - name: requestId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Stored replay artifact
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReplayEnvelope'
              example:
                requestId: 8f5b5b6a-8d10-4a45-89b6-89fb67235d50
                traceId: 2c3fcf67-2fa1-44f1-9ff9-d7ba88ab8dc2
                status: ok
                data:
                  env: demo
                  requestId: 8f5b5b6a-8d10-4a45-89b6-89fb67235d50
                  requestHash: fdbecf0425f79039e4b58f95db95f37dbf5e6136f857f3e71ab6f4d6e93f33d9
                  state: completed
                  traceId: 2c3fcf67-2fa1-44f1-9ff9-d7ba88ab8dc2
                  capabilityId: rag.search@v1
                  reqCanonJson: >-
                    {"caller":{"agentId":"agent-123","role":"researcher"},"capability":"rag.search@v1","payload":{"query":"market
                    risk","topK":3}}
                  reqSha256: fdbecf0425f79039e4b58f95db95f37dbf5e6136f857f3e71ab6f4d6e93f33d9
                  responseJson:
                    results:
                      - id: doc-1
                        text: RelayOrb routes capability invocations through a policy-aware gateway.
                        score: 0.95
                    provider: relayorb-rag
                  status: ok
                  retries: 0
                  latencyMs: 42
                  createdAt: 1738880000
                  updatedAt: 1738880002
components:
  schemas:
    Caller:
      type: object
      required:
        - agentId
        - role
      properties:
        agentId:
          type: string
          minLength: 1
        role:
          type: string
          minLength: 1
        budgetKey:
          type: string
    TraceContext:
      type: object
      properties:
        parentSpanId:
          type: string
    InvokeRequest:
      type: object
      required:
        - requestId
        - caller
        - capability
        - payload
      properties:
        requestId:
          type: string
          minLength: 1
        caller:
          $ref: '#/components/schemas/Caller'
        capability:
          type: string
          minLength: 1
        payload:
          type: object
          additionalProperties: true
        trace:
          $ref: '#/components/schemas/TraceContext'
    SubmitRequest:
      allOf:
        - $ref: '#/components/schemas/InvokeRequest'
        - type: object
          properties:
            callbackUrl:
              type: string
              format: uri
            maxRunMs:
              type: integer
              minimum: 1
            maxAttempts:
              type: integer
              minimum: 1
              maximum: 10
    HealthResponse:
      type: object
      required:
        - requestId
        - traceId
        - status
        - data
      properties:
        requestId:
          type: string
        traceId:
          type: string
        status:
          type: string
          const: ok
        data:
          type: object
          required:
            - service
            - status
          properties:
            service:
              type: string
            status:
              type: string
    InvokeSuccessResponse:
      type: object
      required:
        - requestId
        - traceId
        - status
        - data
      properties:
        requestId:
          type: string
        traceId:
          type: string
        status:
          type: string
          const: ok
        data:
          type: object
          additionalProperties: true
        meta:
          type: object
          additionalProperties: true
    InvokeInProgressResponse:
      type: object
      required:
        - requestId
        - traceId
        - status
        - data
        - meta
      properties:
        requestId:
          type: string
        traceId:
          type: string
        status:
          type: string
          const: ok
        data:
          type: object
          required:
            - state
          properties:
            state:
              type: string
              const: in_progress
        meta:
          type: object
          required:
            - replayed
            - retryAfterMs
            - traceId
          properties:
            replayed:
              type: boolean
            retryAfterMs:
              type: integer
            traceId:
              type: string
    SubmitResponseEnvelope:
      type: object
      required:
        - requestId
        - traceId
        - status
        - data
      properties:
        requestId:
          type: string
        traceId:
          type: string
        status:
          type: string
          const: ok
        data:
          type: object
          required:
            - jobId
            - requestId
            - state
            - statusUrl
            - attempts
            - maxAttempts
          properties:
            jobId:
              type: string
            requestId:
              type: string
            state:
              type: string
            statusUrl:
              type: string
            attempts:
              type: integer
            maxAttempts:
              type: integer
        meta:
          type: object
          additionalProperties: true
    JobStatusEnvelope:
      type: object
      required:
        - requestId
        - traceId
        - status
        - data
      properties:
        requestId:
          type: string
        traceId:
          type: string
        status:
          type: string
          const: ok
        data:
          type: object
          required:
            - jobId
            - requestId
            - capabilityId
            - callerAgentId
            - state
            - attempts
            - maxAttempts
            - createdAt
          properties:
            jobId:
              type: string
            requestId:
              type: string
            capabilityId:
              type: string
            callerAgentId:
              type: string
            state:
              type: string
              enum:
                - queued
                - running
                - succeeded
                - failed
            attempts:
              type: integer
            maxAttempts:
              type: integer
            traceId:
              type: string
            callbackUrl:
              type: string
            createdAt:
              type: integer
            startedAt:
              type: integer
            finishedAt:
              type: integer
            result:
              type: object
              additionalProperties: true
            error:
              type: object
              additionalProperties: true
    ReplayEnvelope:
      type: object
      required:
        - requestId
        - traceId
        - status
        - data
      properties:
        requestId:
          type: string
        traceId:
          type: string
        status:
          type: string
          const: ok
        data:
          type: object
          additionalProperties: true
    ErrorResponse:
      type: object
      required:
        - requestId
        - traceId
        - status
        - error
      properties:
        requestId:
          type: string
        traceId:
          type: string
        status:
          type: string
          const: error
        error:
          type: object
          required:
            - code
            - message
            - details
          properties:
            code:
              $ref: '#/components/schemas/ErrorCode'
            message:
              type: string
            details:
              type: object
              additionalProperties: true
    ErrorCode:
      type: string
      enum:
        - UNAUTHORIZED
        - FORBIDDEN
        - BUDGET_EXCEEDED
        - CAPABILITY_NOT_FOUND
        - NO_HEALTHY_PROVIDERS
        - SCHEMA_VALIDATION_FAILED
        - WORKER_TIMEOUT
        - WORKER_ERROR
        - INTERNAL
