# REST API

> Source: https://docs.synthesized.io/tdk/latest/user_guide/020_guides/advanced_workflows/external_api_reference
> For the complete documentation index, see [llms.txt](https://docs.synthesized.io/llms.txt).

## API Reference

This section provides detailed information about the available endpoints in the Governor REST API.

```yaml
openapi: 3.0.1
info:
  title: "Governor External API"
  description: |
    This API provides external access to the Governor services
  version: "1.0.0"

x-package: io.synthesized.governor.external.api

paths:
  /external/api/v1/healthy:
    get:
      operationId: getExternalHealth
      description: Health check
      tags:
        - external_health
      responses:
        "200":
          description: Healthcheck API
          content:
            application/json:
              schema:
                type: string
                nullable: false

  /external/api/v1/agents:
    get:
      operationId: agents
      tags:
        - external_agents
      deprecated: true
      responses:
        "200":
          description: get list of agents
          content:
            application/json:
              schema:
                type: array
                nullable: false
                items:
                  $ref: "#/components/schemas/ApiAgentResponse"

  /external/api/v1/workers:
    get:
      operationId: workers
      tags:
        - external_workers
      responses:
        "200":
          description: get list of workers
          content:
            application/json:
              schema:
                type: array
                nullable: false
                items:
                  $ref: "#/components/schemas/ApiAgentResponse"

  /external/api/v1/workflow/{workflow_id}/runsync:
    description: Run specified workflow synchronously (and wait for its completion)
    post:
      operationId: runWorkflowSync
      tags:
        - external_workflow_run
      parameters:
        - name: workflow_id
          in: path
          required: true
          schema:
            type: integer
            format: int64
            nullable: false
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiWorkflowRunResponse"

  /external/api/v1/workflow/{workflow_id}/run:
    description: Run specified workflow
    post:
      operationId: runWorkflow
      tags:
        - external_workflow_run
      parameters:
        - name: workflow_id
          in: path
          required: true
          schema:
            type: integer
            format: int64
            nullable: false
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiWorkflowRunResponse"

  /external/api/v1/workflow/{id}/stop:
    description: stop workflow
    post:
      operationId: stopWorkflow
      tags:
        - external_workflow_run
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            format: int64
            nullable: false
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiWorkflowRunResponse"

  /external/api/v1/workflow-run/{id}:
    description: Get workflow run
    get:
      operationId: getWorkflowRun
      tags:
        - external_workflow_run
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            format: int64
            nullable: false
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiWorkflowRunResponse"

  /external/api/v1/workflow/{workflow_id}/runs:
    description: Get workflow runs
    get:
      operationId: listWorkflowRuns
      tags:
        - external_workflow_run
      parameters:
        - name: workflow_id
          in: path
          required: true
          schema:
            type: integer
            format: int64
            nullable: false
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                nullable: false
                items:
                  $ref: "#/components/schemas/ApiWorkflowRunResponse"

  /external/api/v1/workflow-run/{id}/logs:
    description: get workflow run log file.
    get:
      operationId: getWorkflowRunLogs
      tags:
        - external_workflow_run
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            format: int64
            nullable: false
        - in: query
          name: skip
          required: false
          description: >
            The skip lines number is an optional parameter.
          schema:
            type: integer
            format: int32
            nullable: false
            default: 0
        - in: query
          name: limit
          required: false
          description: >
            The fetch size is an optional parameter. When this parameter is not provided,
            it loads all lines from the skip position.
          schema:
            type: integer
            format: int32
            nullable: false
            default: 2147483647
      responses:
        "200":
          description: OK
          content:
            application/octet-stream:
              schema:
                type: string
                nullable: false
  /external/api/v1/spire/refresh-token/bootstrap:
    description: >
      Exchange a just-consumed SPIRE join token for the first refresh token. Governor verifies
      the join token was actually consumed by a real SPIRE attestation before issuing one.
    post:
      operationId: bootstrapRefreshToken
      tags:
        - external_spire_refresh_token
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ApiBootstrapRefreshTokenRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiRefreshTokenResponse"

  /external/api/v1/spire/refresh-token:
    description: >
      Rotate a refresh token: mints a fresh SPIRE join token and a new refresh token,
      superseding the presented one. Presenting an already-superseded token revokes the
      whole rotation chain.
    post:
      operationId: refreshWorkerToken
      tags:
        - external_spire_refresh_token
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ApiRefreshTokenRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiRefreshTokenResponse"

components:
  schemas:
    ApiBootstrapRefreshTokenRequest:
      type: object
      nullable: false
      required:
        - join_token
      properties:
        join_token:
          type: string
          nullable: false
        idempotency_key:
          type: string
          nullable: true
          minLength: 32
          maxLength: 128
          description: >
            Optional, client-generated, retained across retries of one logical bootstrap attempt.
            Lets Governor return the credentials the first attempt issued when its HTTP response was
            lost, instead of refusing the retry because the workload already holds a token it never
            received. Without a matching key, a second exchange of an already-used join token is
            refused.
            Must be unguessable - at least 128 bits from a CSPRNG, e.g. a random UUID. Presenting
            a matching key returns credentials, so a predictable one (a hostname, a counter) is a
            second credential an attacker can guess.

    ApiRefreshTokenRequest:
      type: object
      nullable: false
      required:
        - refresh_token
      properties:
        refresh_token:
          type: string
          nullable: false
        idempotency_key:
          type: string
          nullable: true
          minLength: 32
          maxLength: 128
          description: >
            Optional, client-generated, retained across retries of one logical rotation attempt.
            Lets Governor durably recognize a retry after a lost HTTP response (including across a
            restart or a different replica) and return the credentials the first attempt issued,
            rather than minting a second set. A retry Governor can no longer answer that way is
            rejected with 409. Without a matching key, replaying an already-superseded token is
            treated as reuse and revokes the whole rotation chain.
            Must be unguessable - at least 128 bits from a CSPRNG, e.g. a random UUID. Presenting
            a matching key returns credentials, so a predictable one (a hostname, a counter) is a
            second credential an attacker can guess.

    ApiRefreshTokenResponse:
      type: object
      nullable: false
      required:
        - refresh_token
        - expires_at
      properties:
        join_token:
          type: string
          description: >
            Only present on the ongoing-refresh response. The bootstrap response omits it: the
            worker already has a valid SVID at bootstrap time, so no new join token is needed yet.
        refresh_token:
          type: string
          nullable: false
        expires_at:
          type: string
          format: date-time
          nullable: false

    ApiWorkflowRunResponse:
      type: object
      title: RunWorkflowResponse
      nullable: false
      required:
        - workflow_run_id
      properties:
        workflow_run_id:
          type: integer
          format: int64
        workflow_run_status:
          $ref: '#/components/schemas/ApiWorkflowRunStatus'
        start_date:
          type: string
          format: date-time
          example: "2021-01-30T08:30:00Z"
        end_date:
          type: string
          format: date-time
          example: "2021-01-30T08:30:00Z"
        user_id:
          type: integer
          format: int64
        error_message:
          type: string

    ApiAgentResponse:
      type: object
      nullable: false
      required:
        - id
        - status
        - tags
        - data_source_tags
      properties:
        id:
          type: string
          nullable: false
        status:
          $ref: "#/components/schemas/ApiAgentStatus"
        tags:
          type: string
          nullable: false
        data_source_tags:
          type: string
          nullable: false
        latest_heartbeat:
          type: string
          format: date-time

    ApiAgentStatus:
      nullable: false
      enum:
        - ALIVE

    ApiWorkflowRunStatus:
      enum:
        - QUEUED
        - RUNNING
        - COMPLETED
        - FAILED
        - STOPPING
        - STOPPED
```

## Authorization

To use the Governor REST API, you must include a valid access key in the request headers. This key authenticates the request and identifies the user making the request.

You can generate an access key from the Governor web interface. For instructions on how to do this, see [Generating an Access Key](https://docs.synthesized.io/tdk/latest/user_guide/020_guides/advanced_workflows/public_api_access_keys).

Include the access key in the `X-Access-Key` header of your request.

## Examples

### Check API availability

The health check endpoint allows you to verify the accessibility of the API. To use this endpoint, replace `{your_access_key}` with the actual access key you generated in the previous step.

If you’re deploying with [Docker Compose](https://docs.synthesized.io/tdk/latest/user_guide/050_deployment_operations/production_setup/dc), you should replace "https://your-domain.com" with `localhost:8081`. This ensures that you’re making requests to the correct location where your application is running when using Docker Compose.

```shell
curl -X GET \
  https://your-domain.com/external/api/v1/healthy \
  -H 'X-Access-Key: {your_access_key}'
```

The expected response is: `OK`

### Run workflow

To run a workflow, replace `{workflow_id}` with the ID of the workflow you want to run and `{your_access_key}` with your actual access key.

```shell
curl -X POST \
  https://your-domain.com/external/api/v1/workflow/{workflow_id}/run \
  -H 'X-Access-Key: {your_access_key}'
```

The response will look something like this:

```json
{
  "workflow_run_id": 1,
  "workflow_run_status": "RUNNING",
  "start_date": "2024-04-25T22:24:52.84434075Z",
  "end_date": null,
  "user_id": 1,
  "error_message": null
}
```

To run the workflow on a worker, add `useAgents=true` to the request URL:

```shell
curl -X POST \
  https://your-domain.com/external/api/v1/workflow/{workflow_id}/run?useAgents=true \
  -H 'X-Access-Key: {your_access_key}'
```

### Stop workflow

To stop a workflow, replace `{workflow_id}` with the ID of the workflow you want to stop and `{your_access_key}` with your actual access key.

```shell
curl -X POST \
  https://your-domain.com/external/api/v1/workflow/{workflow_id}/stop \
  -H 'X-Access-Key: {your_access_key}'
```

The response will look something like this:

```json
{
  "workflow_run_id": 1,
  "workflow_run_status": "STOPPING",
  "start_date": "2024-04-25T22:24:52.84434075Z",
  "end_date": "2024-04-25T22:25:52.84434075Z",
  "user_id": 1,
  "error_message": null
}
```

### Get workflow run status

To get the status of a workflow run, replace `{workflow_run_id}` with the ID of the workflow run from the previous step and `{your_access_key}` with your actual access key.

```shell
curl -X GET \
  https://your-domain.com/external/api/v1/workflow-run/{workflow_run_id} \
  -H 'X-Access-Key: {your_access_key}'
```

The response will look something like this:

```json
{
  "workflow_run_id": 1,
  "workflow_run_status": "COMPLETED",
  "start_date": "2024-04-25T22:24:52.844341Z",
  "end_date": "2024-04-25T22:25:04.863765Z",
  "user_id": 1,
  "error_message": null
}
```

### Get all workflow runs by workflow ID

To get all workflow runs for workflow, replace `{workflow_id}` with the ID of the workflow run from the previous step and `{your_access_key}` with your actual access key.

```shell
curl -X GET \
  https://your-domain.com/external/api/v1/workflow/{workflow_id}/runs \
  -H 'X-Access-Key: {your_access_key}'
```

The response will look something like this:

```json
[
    {
      "workflow_run_id": 1,
      "workflow_run_status": "COMPLETED",
      "start_date": "2024-04-25T22:24:52.844341Z",
      "end_date": "2024-04-25T22:25:04.863765Z",
      "user_id": 1,
      "error_message": null
    }
]
```

## Get workflow logs

This API retrieves the log file for a specific workflow run. You can optionally skip lines or limit the number of lines returned.

Get full log file:

```shell
curl -X GET \
  https://your-domain.com/external/api/v1/workflow-run/{workflow_id}/logs \
  -H 'X-Access-Key: {your_access_key}'
```

Fetch only 50 lines (After skipping 100):

```shell
curl -X GET \
"https://your-domain.com/external/api/v1/workflow-run/{workflow_id}/logs?skip=100&limit=50" \
-H "X-Access-Key: {your_access_key}"
```

The response will look something like this:

```text
25-04-2025 13:22:36.768 INFO  i.s.g.f.w.service.WorkflowRunner [][][] - Workflow runner version: Synthesized TDK 1.95
25-04-2025 13:22:36.769 DEBUG i.s.g.f.w.service.WorkflowRunner [][][] - Workflow run 3494 started
25-04-2025 13:22:36.808 DEBUG i.s.tdk.executor.SynthesizedExecutor [][][] - Changing state: INIT -> SCHEMA_COPY
25-04-2025 13:22:36.836 DEBUG i.s.t.d.c.DetailsDatasourceContainer [][][] - Waiting 20000 ms for #12_jdbc_postgresql_cloud-main-spawn_postgres_database_azure_com_5432 to acquire 20 connections
25-04-2025 13:22:36.836 INFO  com.zaxxer.hikari.HikariDataSource [][][] - #12_jdbc_postgresql_cloud-main-spawn_postgres_database_azure_com_5432 - Starting...
...
```

## Get all connected workers

To see all active workers, make this API call with your `{your_access_key}` access key:

```shell
curl -X GET \
  https://your-domain.com/external/api/v1/agents \
  -H 'X-Access-Key: {your_access_key}'
```

The response will look something like this:

```json
[
    {
      "id": "00000000-00000000-00000000-00000000",
      "status": "ALIVE",
      "tags": "postgresql,production,eu-west",
      "data_source_tags": "postgresql,api-gateway",
      "latest_heartbeat": "2023-11-15T14:30:45Z"
    }
]
```
