You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by di...@apache.org on 2026/09/28 12:39:00 UTC
(airavata) branch refactor-single-module updated: Adding openapi yaml for the API
This is an automated email from the ASF dual-hosted git repository.
DImuthuUpe pushed a commit to branch refactor-single-module
in repository https://gitbox.apache.org/repos/asf/airavata.git
The following commit(s) were added to refs/heads/refactor-single-module by this push:
new 8710cb4fbe Adding openapi yaml for the API
8710cb4fbe is described below
commit 8710cb4fbe60907693510474e202f99b2d79e290
Author: DImuthuUpe <di...@gmail.com>
AuthorDate: Mon Sep 28 08:38:51 2026 -0400
Adding openapi yaml for the API
---
docs/api.md | 2 +
docs/openapi.yaml | 3727 +++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 3729 insertions(+)
diff --git a/docs/api.md b/docs/api.md
index 7233d36f3a..6048fedd25 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -2,6 +2,8 @@
Base URL: `http://localhost:9095` (default `SERVER_PORT` is `9095`; override via the `SERVER_PORT` env var).
+This page is the narrative reference, with a worked `curl` example per endpoint. [`openapi.yaml`](openapi.yaml) beside it is the same API as a machine-readable OpenAPI 3.1 description, for client generation and for loading into Swagger UI or Redoc.
+
All request/response bodies are JSON (`Content-Type: application/json`). Writes require an `Authorization: Bearer <token>` header for a principal with `ADMIN` or `SUPER_ADMIN` authority; catalog reads (`GET`) are open without a token. [SSH keys](#ssh-keys), [groups](#groups), [cluster configs](#slurm-cluster-configs), [data products](#data-products) and [SCP data storages](#scp-data-storages) are the exception on both counts — they are reached through ownership (and, for all but a key, s [...]
## Error responses
diff --git a/docs/openapi.yaml b/docs/openapi.yaml
new file mode 100644
index 0000000000..836a27c4d2
--- /dev/null
+++ b/docs/openapi.yaml
@@ -0,0 +1,3727 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+openapi: 3.1.0
+
+info:
+ title: Apache Airavata HTTP API
+ version: "1.0.0"
+ description: |
+ The REST API served by `airavata-server` (see `internal/server/server.go`).
+
+ All request and response bodies are JSON (`Content-Type: application/json`).
+
+ ## Authentication
+
+ Authentication is a bearer token validated by CILogon introspection, carried as
+ `Authorization: Bearer <token>`. A request without a token is anonymous rather
+ than rejected at the door: each operation decides for itself whether an anonymous
+ caller is acceptable. A token that is present but invalid is always `401`, with
+ `WWW-Authenticate: Bearer error="invalid_token"`. If the identity provider cannot
+ be reached the call fails `502`, because that is not the caller's fault.
+
+ ## Authorization
+
+ Two schemes are in play, depending on the resource:
+
+ - **Platform roles** (`ADMIN`, `SUPER_ADMIN`) guard the shared catalogue —
+ Slurm clusters, partitions, application templates and batch deployments.
+ Reads of the catalogue are open to anyone, including anonymous callers;
+ writes require a role.
+ - **Ownership and sharing** guards the per-user resources — SSH keys, groups,
+ cluster configs, SCP data storages, data products and processes. Ownership
+ comes from the token and is never carried in a request body, so nothing can be
+ created on someone else's behalf or handed over by editing it. None of these
+ are readable anonymously.
+
+ SSH keys are the strictest case: platform admins are not treated as owners, and
+ there is no share that opens a key up. Private key material and passphrases are
+ write-only — no read endpoint has a field for them.
+
+ ## Errors
+
+ Every failure uses the same envelope (`Error`). Request-body validation adds a
+ `fieldErrors` array naming every constraint that failed, using the JSON path of
+ the offending field including indexes and nesting (`inputs[0].inputType`,
+ `defaultBatchJobConfig.wallTimeMinutes`).
+
+ Internal failures return `500` with a fixed `"Internal server error"` message;
+ the underlying detail is logged rather than returned, since it may name internal
+ state.
+ license:
+ name: Apache License 2.0
+ url: https://www.apache.org/licenses/LICENSE-2.0
+
+servers:
+ - url: http://localhost:9095
+ description: Local development server (override the port with `SERVER_PORT`)
+
+tags:
+ - name: Health
+ description: Liveness probe.
+ - name: Users
+ description: User accounts.
+ - name: Groups
+ description: Groups and their membership; the subject of a group share.
+ - name: SSH Keys
+ description: Registered keypairs. Private material is write-only.
+ - name: Slurm Clusters
+ description: The machine catalogue and its partitions.
+ - name: Slurm Cluster Configs
+ description: Who a job submits as on a cluster, and with which key.
+ - name: Application Templates
+ description: An application's input/output contract, independent of where it runs.
+ - name: Batch Deployments
+ description: A template bound to a cluster, with the resource request a run starts from.
+ - name: SCP Data Storages
+ description: Hosts data is staged to and from over SCP.
+ - name: Data Products
+ description: Registered datasets on a storage.
+ - name: Processes
+ description: Runs, their status history and their tasks.
+
+security:
+ - bearerAuth: []
+
+paths:
+ /health:
+ get:
+ tags: [Health]
+ summary: Liveness probe
+ description: |
+ No token is required. The authentication middleware wraps the whole mux
+ rather than individual routes, so a request that does carry a token still has
+ it validated — call this without an `Authorization` header to probe liveness
+ independently of the identity provider.
+ operationId: health
+ security: []
+ responses:
+ "200":
+ description: The server is up.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ const: UP
+ required: [status]
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+
+ # ---------------------------------------------------------------- Users
+
+ /api/v1/users:
+ get:
+ tags: [Users]
+ summary: List users
+ description: Requires `ADMIN` or `SUPER_ADMIN` authority.
+ operationId: listUsers
+ responses:
+ "200":
+ description: Every registered user.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/UserResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Users]
+ summary: Register a user
+ description: |
+ Creates the `users` row an owned resource is attached to. Owning anything in
+ this API requires such a row.
+ operationId: createUser
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/UserRegistration" }
+ responses:
+ "200":
+ description: The registered user.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/UserResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/users/{userId}:
+ parameters:
+ - $ref: "#/components/parameters/UserId"
+ get:
+ tags: [Users]
+ summary: Read a user
+ description: |
+ The response carries no email, auth method or roles — widening it would leak
+ more about an account than this API ever has.
+ operationId: getUser
+ responses:
+ "200":
+ description: The user.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/UserResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Users]
+ summary: Update a user
+ description: |
+ Update the user with the provided information.
+ operationId: updateUser
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/UserRegistration" }
+ responses:
+ "200":
+ description: The updated user.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/UserResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ # --------------------------------------------------------------- Groups
+
+ /api/v1/groups:
+ get:
+ tags: [Groups]
+ summary: List all groups
+ description: Requires `ADMIN` or `SUPER_ADMIN` authority.
+ operationId: listGroups
+ responses:
+ "200":
+ description: Every group.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/GroupResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Groups]
+ summary: Create a group
+ description: |
+ The owner is taken from the token — there is no owner field — and is not
+ transferable afterwards.
+ operationId: createGroup
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/GroupRequest" }
+ responses:
+ "201":
+ description: The created group.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/GroupResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/groups/me:
+ get:
+ tags: [Groups]
+ summary: List the caller's groups
+ description: The groups the caller owns or is a member of.
+ operationId: listMyGroups
+ responses:
+ "200":
+ description: The caller's groups.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/GroupResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/groups/{groupId}:
+ parameters:
+ - $ref: "#/components/parameters/GroupId"
+ get:
+ tags: [Groups]
+ summary: Read a group
+ description: |
+ A caller with no standing to know the group exists gets `404` rather than
+ `403`. Members are not inlined; read them through the members sub-resource.
+ operationId: getGroup
+ responses:
+ "200":
+ description: The group.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/GroupResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ put:
+ tags: [Groups]
+ summary: Update a group
+ operationId: updateGroup
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/GroupRequest" }
+ responses:
+ "200":
+ description: The updated group.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/GroupResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Groups]
+ summary: Delete a group
+ operationId: deleteGroup
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "409": { $ref: "#/components/responses/Conflict" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/groups/{groupId}/members:
+ parameters:
+ - $ref: "#/components/parameters/GroupId"
+ get:
+ tags: [Groups]
+ summary: List group members
+ operationId: listGroupMembers
+ responses:
+ "200":
+ description: The group's memberships.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/GroupMemberResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Groups]
+ summary: Add a group member
+ description: |
+ An omitted `groupRole` admits a plain `MEMBER`; an omitted
+ `groupMemberStatus` admits them as `ACTIVE`.
+ operationId: addGroupMember
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/GroupMemberRequest" }
+ responses:
+ "201":
+ description: The created membership.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/GroupMemberResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/groups/{groupId}/members/{userId}:
+ parameters:
+ - $ref: "#/components/parameters/GroupId"
+ - $ref: "#/components/parameters/UserId"
+ get:
+ tags: [Groups]
+ summary: Read one membership
+ operationId: getGroupMember
+ responses:
+ "200":
+ description: The membership.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/GroupMemberResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ put:
+ tags: [Groups]
+ summary: Update a membership
+ description: |
+ Both fields are optional and an omitted one is left as it stands, so a member
+ can be suspended without restating their role. The user is identified by the
+ path, so a membership cannot be moved to a different user by editing it.
+ operationId: updateGroupMember
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/GroupMemberUpdate" }
+ responses:
+ "200":
+ description: The updated membership.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/GroupMemberResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Groups]
+ summary: Remove a member from a group
+ operationId: removeGroupMember
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ # ------------------------------------------------------------- SSH keys
+
+ /api/v1/ssh-keys:
+ get:
+ tags: [SSH Keys]
+ summary: List the caller's SSH keys
+ description: Returns only the caller's own keys. Admins are not exempt.
+ operationId: listSSHKeys
+ responses:
+ "200":
+ description: The caller's keys.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/SSHKeyResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [SSH Keys]
+ summary: Register an SSH key
+ description: |
+ Self-service for any authenticated caller with a `users` row. `privateKey` is
+ required on create; `privateKey` and `passphrase` are write-only and are never
+ returned by any read endpoint.
+ operationId: createSSHKey
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SSHKeyRequest" }
+ responses:
+ "201":
+ description: The registered key, without its private material.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SSHKeyResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/ssh-keys/{sshKeyId}:
+ parameters:
+ - $ref: "#/components/parameters/SSHKeyId"
+ get:
+ tags: [SSH Keys]
+ summary: Read an SSH key
+ description: Owner only.
+ operationId: getSSHKey
+ responses:
+ "200":
+ description: The key, without its private material.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SSHKeyResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ put:
+ tags: [SSH Keys]
+ summary: Update an SSH key
+ description: |
+ Owner only. An update that omits `privateKey` keeps the stored secret.
+ operationId: updateSSHKey
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SSHKeyRequest" }
+ responses:
+ "200":
+ description: The updated key.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SSHKeyResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [SSH Keys]
+ summary: Delete an SSH key
+ description: |
+ Owner only. A key still referenced by a cluster config or a data storage
+ cannot be deleted: `409 Conflict`.
+ operationId: deleteSSHKey
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "409": { $ref: "#/components/responses/Conflict" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ # ------------------------------------------------------- Slurm clusters
+
+ /api/v1/slurm-clusters:
+ get:
+ tags: [Slurm Clusters]
+ summary: List Slurm clusters
+ description: Catalogue read; open without a token.
+ operationId: listSlurmClusters
+ security:
+ - {}
+ - bearerAuth: []
+ responses:
+ "200":
+ description: Every registered cluster, with its partitions inlined.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/SlurmClusterResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Slurm Clusters]
+ summary: Register a Slurm cluster
+ description: |
+ Requires `ADMIN` or `SUPER_ADMIN` authority. Describes the machine only — the
+ account a job submits under and the key it presents are a cluster config, so
+ registering a cluster never puts anyone's credentials into the catalogue.
+
+ `partitions` is accepted on create so a cluster whose layout is already known
+ arrives complete.
+ operationId: createSlurmCluster
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SlurmClusterRequest" }
+ responses:
+ "201":
+ description: The registered cluster.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SlurmClusterResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/slurm-clusters/{slurmClusterId}:
+ parameters:
+ - $ref: "#/components/parameters/SlurmClusterId"
+ get:
+ tags: [Slurm Clusters]
+ summary: Read a Slurm cluster
+ description: Catalogue read; open without a token.
+ operationId: getSlurmCluster
+ security:
+ - {}
+ - bearerAuth: []
+ responses:
+ "200":
+ description: The cluster, with its partitions inlined.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SlurmClusterResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ put:
+ tags: [Slurm Clusters]
+ summary: Update a Slurm cluster
+ description: |
+ Requires `ADMIN` or `SUPER_ADMIN` authority. An update carrying `partitions`
+ is rejected: the set has no ids to match rows by, and replacing the collection
+ wholesale would delete partitions the caller never mentioned. Use the
+ partition sub-resource instead.
+ operationId: updateSlurmCluster
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SlurmClusterRequest" }
+ responses:
+ "200":
+ description: The updated cluster.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SlurmClusterResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Slurm Clusters]
+ summary: Delete a Slurm cluster
+ description: Requires `ADMIN` or `SUPER_ADMIN` authority.
+ operationId: deleteSlurmCluster
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "409": { $ref: "#/components/responses/Conflict" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/slurm-clusters/{slurmClusterId}/partitions:
+ parameters:
+ - $ref: "#/components/parameters/SlurmClusterId"
+ get:
+ tags: [Slurm Clusters]
+ summary: List a cluster's partitions
+ description: Catalogue read; open without a token.
+ operationId: listClusterPartitions
+ security:
+ - {}
+ - bearerAuth: []
+ responses:
+ "200":
+ description: The cluster's partitions.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/ClusterPartitionResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Slurm Clusters]
+ summary: Add a partition to a cluster
+ description: Requires `ADMIN` or `SUPER_ADMIN` authority.
+ operationId: createClusterPartition
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/ClusterPartitionRequest" }
+ responses:
+ "201":
+ description: The created partition.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/ClusterPartitionResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/slurm-clusters/{slurmClusterId}/partitions/{partitionId}:
+ parameters:
+ - $ref: "#/components/parameters/SlurmClusterId"
+ - $ref: "#/components/parameters/PartitionId"
+ get:
+ tags: [Slurm Clusters]
+ summary: Read a partition
+ description: Catalogue read; open without a token.
+ operationId: getClusterPartition
+ security:
+ - {}
+ - bearerAuth: []
+ responses:
+ "200":
+ description: The partition.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/ClusterPartitionResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ put:
+ tags: [Slurm Clusters]
+ summary: Update a partition
+ description: Requires `ADMIN` or `SUPER_ADMIN` authority.
+ operationId: updateClusterPartition
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/ClusterPartitionRequest" }
+ responses:
+ "200":
+ description: The updated partition.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/ClusterPartitionResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Slurm Clusters]
+ summary: Delete a partition
+ description: Requires `ADMIN` or `SUPER_ADMIN` authority.
+ operationId: deleteClusterPartition
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ # ------------------------------------------------- Slurm cluster configs
+
+ /api/v1/slurm-cluster-configs:
+ get:
+ tags: [Slurm Cluster Configs]
+ summary: List all cluster configs
+ description: Requires `ADMIN` or `SUPER_ADMIN` authority.
+ operationId: listSlurmClusterConfigs
+ responses:
+ "200":
+ description: Every config.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ { $ref: "#/components/schemas/SlurmClusterConfigResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Slurm Cluster Configs]
+ summary: Create a cluster config
+ description: |
+ Names the account a job submits under on a cluster, the key it presents and
+ the directory it works beneath. The owner comes from the token. The key must
+ be one the caller owns — assigning someone else's key is `403`.
+ operationId: createSlurmClusterConfig
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SlurmClusterConfigRequest" }
+ responses:
+ "201":
+ description: The created config.
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/SlurmClusterConfigResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/slurm-cluster-configs/me:
+ get:
+ tags: [Slurm Cluster Configs]
+ summary: List the caller's cluster configs
+ operationId: listMySlurmClusterConfigs
+ responses:
+ "200":
+ description: The configs the caller owns.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ { $ref: "#/components/schemas/SlurmClusterConfigResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/slurm-cluster-configs/shared-with-me:
+ get:
+ tags: [Slurm Cluster Configs]
+ summary: List cluster configs shared with the caller
+ description: |
+ Each entry carries `permission`, the caller's effective standing — the
+ strongest of the shares reaching them. A group share counts only while the
+ membership is `ACTIVE`.
+ operationId: listSlurmClusterConfigsSharedWithMe
+ responses:
+ "200":
+ description: The configs shared with the caller.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ { $ref: "#/components/schemas/SlurmClusterConfigResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/slurm-cluster-configs/{slurmClusterConfigId}:
+ parameters:
+ - $ref: "#/components/parameters/SlurmClusterConfigId"
+ get:
+ tags: [Slurm Cluster Configs]
+ summary: Read a cluster config
+ description: |
+ Requires a `READ` standing: owner, admin, or a share. The cluster is inlined,
+ and the key as its safe summary — name and public key only.
+ operationId: getSlurmClusterConfig
+ responses:
+ "200":
+ description: The config.
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/SlurmClusterConfigResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ put:
+ tags: [Slurm Cluster Configs]
+ summary: Update a cluster config
+ description: |
+ Requires a `WRITE` standing. Re-presenting the key the config already holds is
+ not an assignment, which is what lets a `WRITE` grantee edit a config without
+ owning the key on it.
+ operationId: updateSlurmClusterConfig
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SlurmClusterConfigRequest" }
+ responses:
+ "200":
+ description: The updated config.
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/SlurmClusterConfigResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Slurm Cluster Configs]
+ summary: Delete a cluster config
+ description: Owner or admin.
+ operationId: deleteSlurmClusterConfig
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "409": { $ref: "#/components/responses/Conflict" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/slurm-cluster-configs/{slurmClusterConfigId}/group-shares:
+ parameters:
+ - $ref: "#/components/parameters/SlurmClusterConfigId"
+ get:
+ tags: [Slurm Cluster Configs]
+ summary: List a config's group shares
+ description: Owner or admin.
+ operationId: listSlurmClusterConfigGroupShares
+ responses:
+ "200":
+ description: The group shares on this config.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ {
+ $ref: "#/components/schemas/SlurmClusterConfigGroupSharingResponse",
+ }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Slurm Cluster Configs]
+ summary: Share a config with a group
+ description: An omitted `permission` grants `READ`.
+ operationId: shareSlurmClusterConfigWithGroup
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ {
+ $ref: "#/components/schemas/SlurmClusterConfigGroupSharingRequest",
+ }
+ responses:
+ "201":
+ description: The created share.
+ content:
+ application/json:
+ schema:
+ {
+ $ref: "#/components/schemas/SlurmClusterConfigGroupSharingResponse",
+ }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/slurm-cluster-configs/{slurmClusterConfigId}/group-shares/{sharingId}:
+ parameters:
+ - $ref: "#/components/parameters/SlurmClusterConfigId"
+ - $ref: "#/components/parameters/SharingId"
+ put:
+ tags: [Slurm Cluster Configs]
+ summary: Change what a group share grants
+ description: The subject is fixed at creation; only the permission is editable.
+ operationId: updateSlurmClusterConfigGroupShare
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/SlurmClusterConfigSharingUpdate" }
+ responses:
+ "200":
+ description: The updated share.
+ content:
+ application/json:
+ schema:
+ {
+ $ref: "#/components/schemas/SlurmClusterConfigGroupSharingResponse",
+ }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Slurm Cluster Configs]
+ summary: Revoke a group share
+ operationId: revokeSlurmClusterConfigGroupShare
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/slurm-cluster-configs/{slurmClusterConfigId}/user-shares:
+ parameters:
+ - $ref: "#/components/parameters/SlurmClusterConfigId"
+ get:
+ tags: [Slurm Cluster Configs]
+ summary: List a config's user shares
+ description: Owner or admin.
+ operationId: listSlurmClusterConfigUserShares
+ responses:
+ "200":
+ description: The user shares on this config.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ {
+ $ref: "#/components/schemas/SlurmClusterConfigUserSharingResponse",
+ }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Slurm Cluster Configs]
+ summary: Share a config with one user
+ description: An omitted `permission` grants `READ`.
+ operationId: shareSlurmClusterConfigWithUser
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ {
+ $ref: "#/components/schemas/SlurmClusterConfigUserSharingRequest",
+ }
+ responses:
+ "201":
+ description: The created share.
+ content:
+ application/json:
+ schema:
+ {
+ $ref: "#/components/schemas/SlurmClusterConfigUserSharingResponse",
+ }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/slurm-cluster-configs/{slurmClusterConfigId}/user-shares/{sharingId}:
+ parameters:
+ - $ref: "#/components/parameters/SlurmClusterConfigId"
+ - $ref: "#/components/parameters/SharingId"
+ put:
+ tags: [Slurm Cluster Configs]
+ summary: Change what a user share grants
+ operationId: updateSlurmClusterConfigUserShare
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/SlurmClusterConfigSharingUpdate" }
+ responses:
+ "200":
+ description: The updated share.
+ content:
+ application/json:
+ schema:
+ {
+ $ref: "#/components/schemas/SlurmClusterConfigUserSharingResponse",
+ }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Slurm Cluster Configs]
+ summary: Revoke a user share
+ operationId: revokeSlurmClusterConfigUserShare
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ # ------------------------------------------------- Application templates
+
+ /api/v1/application-templates:
+ get:
+ tags: [Application Templates]
+ summary: List application templates
+ description: Catalogue read; open without a token.
+ operationId: listApplicationTemplates
+ security:
+ - {}
+ - bearerAuth: []
+ responses:
+ "200":
+ description: Every template, with its declarations inlined.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/TemplateResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Application Templates]
+ summary: Create an application template
+ description: |
+ Requires `ADMIN` or `SUPER_ADMIN` authority. Input names must be unique within
+ a template.
+ operationId: createApplicationTemplate
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/TemplateRequest" }
+ responses:
+ "201":
+ description: The created template, with server-assigned declaration ids.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/TemplateResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/application-templates/{templateId}:
+ parameters:
+ - $ref: "#/components/parameters/TemplateId"
+ get:
+ tags: [Application Templates]
+ summary: Read an application template
+ description: Catalogue read; open without a token.
+ operationId: getApplicationTemplate
+ security:
+ - {}
+ - bearerAuth: []
+ responses:
+ "200":
+ description: The template.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/TemplateResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ put:
+ tags: [Application Templates]
+ summary: Update an application template
+ description: |
+ Requires `ADMIN` or `SUPER_ADMIN` authority. Declarations are replaced
+ wholesale: `inputId` and `outputId` in the body are ignored and child ids are
+ regenerated rather than matched.
+ operationId: updateApplicationTemplate
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/TemplateRequest" }
+ responses:
+ "200":
+ description: The updated template.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/TemplateResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Application Templates]
+ summary: Delete an application template
+ description: |
+ Requires `ADMIN` or `SUPER_ADMIN` authority. A template that still has
+ deployments cannot be deleted: `409 Conflict`.
+ operationId: deleteApplicationTemplate
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "409": { $ref: "#/components/responses/Conflict" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ # ----------------------------------------------------- Batch deployments
+
+ /api/v1/slurm-deployments:
+ get:
+ tags: [Batch Deployments]
+ summary: List batch deployments
+ description: |
+ Catalogue read; open without a token. Supply `templateId` to narrow the
+ listing to one template's deployments.
+ operationId: listBatchDeployments
+ security:
+ - {}
+ - bearerAuth: []
+ parameters:
+ - name: templateId
+ in: query
+ required: false
+ description: Return only the deployments of this template.
+ schema: { type: string }
+ responses:
+ "200":
+ description: The matching deployments.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/BatchDeploymentResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Batch Deployments]
+ summary: Create a batch deployment
+ description: |
+ Requires `ADMIN` or `SUPER_ADMIN` authority. Binds a template to a cluster,
+ with the run script section and the resource request a run of it starts from.
+ Which identity a job submits under is a property of the run, so it is named on
+ the process instead.
+ operationId: createBatchDeployment
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/BatchDeploymentRequest" }
+ responses:
+ "201":
+ description: The created deployment.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/BatchDeploymentResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/slurm-deployments/{deploymentId}:
+ parameters:
+ - $ref: "#/components/parameters/DeploymentId"
+ get:
+ tags: [Batch Deployments]
+ summary: Read a batch deployment
+ description: Catalogue read; open without a token.
+ operationId: getBatchDeployment
+ security:
+ - {}
+ - bearerAuth: []
+ responses:
+ "200":
+ description: The deployment.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/BatchDeploymentResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ put:
+ tags: [Batch Deployments]
+ summary: Update a batch deployment
+ description: Requires `ADMIN` or `SUPER_ADMIN` authority.
+ operationId: updateBatchDeployment
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/BatchDeploymentRequest" }
+ responses:
+ "200":
+ description: The updated deployment.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/BatchDeploymentResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Batch Deployments]
+ summary: Delete a batch deployment
+ description: Requires `ADMIN` or `SUPER_ADMIN` authority.
+ operationId: deleteBatchDeployment
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "409": { $ref: "#/components/responses/Conflict" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ # ----------------------------------------------------- SCP data storages
+
+ /api/v1/scp-data-storages:
+ get:
+ tags: [SCP Data Storages]
+ summary: List all SCP data storages
+ description: Requires `ADMIN` or `SUPER_ADMIN` authority.
+ operationId: listSCPDataStorages
+ responses:
+ "200":
+ description: Every storage.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/SCPDataStorageResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [SCP Data Storages]
+ summary: Register an SCP data storage
+ description: |
+ The owner comes from the token. The key must be one the caller owns. An
+ omitted `port` means 22.
+ operationId: createSCPDataStorage
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SCPDataStorageRequest" }
+ responses:
+ "201":
+ description: The registered storage.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SCPDataStorageResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/scp-data-storages/me:
+ get:
+ tags: [SCP Data Storages]
+ summary: List the caller's SCP data storages
+ operationId: listMySCPDataStorages
+ responses:
+ "200":
+ description: The storages the caller owns.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/SCPDataStorageResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/scp-data-storages/shared-with-me:
+ get:
+ tags: [SCP Data Storages]
+ summary: List SCP data storages shared with the caller
+ description: |
+ Each entry carries `permission`, the caller's effective standing. `WRITE`
+ implies `READ`, and where several shares reach the same caller the strongest
+ applies.
+ operationId: listSCPDataStoragesSharedWithMe
+ responses:
+ "200":
+ description: The storages shared with the caller.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/SCPDataStorageResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/scp-data-storages/{dataStorageId}:
+ parameters:
+ - $ref: "#/components/parameters/DataStorageId"
+ get:
+ tags: [SCP Data Storages]
+ summary: Read an SCP data storage
+ description: Requires a `READ` standing. The key is inlined as its safe summary.
+ operationId: getSCPDataStorage
+ responses:
+ "200":
+ description: The storage.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SCPDataStorageResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ put:
+ tags: [SCP Data Storages]
+ summary: Update an SCP data storage
+ description: Requires a `WRITE` standing.
+ operationId: updateSCPDataStorage
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SCPDataStorageRequest" }
+ responses:
+ "200":
+ description: The updated storage.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SCPDataStorageResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [SCP Data Storages]
+ summary: Delete an SCP data storage
+ description: Owner or admin.
+ operationId: deleteSCPDataStorage
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "409": { $ref: "#/components/responses/Conflict" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/scp-data-storages/{dataStorageId}/group-shares:
+ parameters:
+ - $ref: "#/components/parameters/DataStorageId"
+ get:
+ tags: [SCP Data Storages]
+ summary: List a storage's group shares
+ description: Owner or admin.
+ operationId: listSCPDataStorageGroupShares
+ responses:
+ "200":
+ description: The group shares on this storage.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ {
+ $ref: "#/components/schemas/SCPDataStorageGroupSharingResponse",
+ }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [SCP Data Storages]
+ summary: Share a storage with a group
+ description: An omitted `permission` grants `READ`.
+ operationId: shareSCPDataStorageWithGroup
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/SCPDataStorageGroupSharingRequest" }
+ responses:
+ "201":
+ description: The created share.
+ content:
+ application/json:
+ schema:
+ {
+ $ref: "#/components/schemas/SCPDataStorageGroupSharingResponse",
+ }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/scp-data-storages/{dataStorageId}/group-shares/{sharingId}:
+ parameters:
+ - $ref: "#/components/parameters/DataStorageId"
+ - $ref: "#/components/parameters/SharingId"
+ put:
+ tags: [SCP Data Storages]
+ summary: Change what a group share grants
+ operationId: updateSCPDataStorageGroupShare
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SCPDataStorageSharingUpdate" }
+ responses:
+ "200":
+ description: The updated share.
+ content:
+ application/json:
+ schema:
+ {
+ $ref: "#/components/schemas/SCPDataStorageGroupSharingResponse",
+ }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [SCP Data Storages]
+ summary: Revoke a group share
+ operationId: revokeSCPDataStorageGroupShare
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/scp-data-storages/{dataStorageId}/user-shares:
+ parameters:
+ - $ref: "#/components/parameters/DataStorageId"
+ get:
+ tags: [SCP Data Storages]
+ summary: List a storage's user shares
+ description: Owner or admin.
+ operationId: listSCPDataStorageUserShares
+ responses:
+ "200":
+ description: The user shares on this storage.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ {
+ $ref: "#/components/schemas/SCPDataStorageUserSharingResponse",
+ }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [SCP Data Storages]
+ summary: Share a storage with one user
+ description: An omitted `permission` grants `READ`.
+ operationId: shareSCPDataStorageWithUser
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/SCPDataStorageUserSharingRequest" }
+ responses:
+ "201":
+ description: The created share.
+ content:
+ application/json:
+ schema:
+ {
+ $ref: "#/components/schemas/SCPDataStorageUserSharingResponse",
+ }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/scp-data-storages/{dataStorageId}/user-shares/{sharingId}:
+ parameters:
+ - $ref: "#/components/parameters/DataStorageId"
+ - $ref: "#/components/parameters/SharingId"
+ put:
+ tags: [SCP Data Storages]
+ summary: Change what a user share grants
+ operationId: updateSCPDataStorageUserShare
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/SCPDataStorageSharingUpdate" }
+ responses:
+ "200":
+ description: The updated share.
+ content:
+ application/json:
+ schema:
+ {
+ $ref: "#/components/schemas/SCPDataStorageUserSharingResponse",
+ }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [SCP Data Storages]
+ summary: Revoke a user share
+ operationId: revokeSCPDataStorageUserShare
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ # --------------------------------------------------------- Data products
+
+ /api/v1/data-products:
+ get:
+ tags: [Data Products]
+ summary: List all data products
+ description: Requires `ADMIN` or `SUPER_ADMIN` authority.
+ operationId: listDataProducts
+ responses:
+ "200":
+ description: Every registered dataset.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/DataProductResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Data Products]
+ summary: Register a data product
+ description: |
+ A path on an SCP data storage. Ownership comes from the token and the
+ provision status is the server's to move, so neither is accepted in the body.
+ operationId: createDataProduct
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/DataProductRequest" }
+ responses:
+ "201":
+ description: The registered dataset.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/DataProductResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/data-products/me:
+ get:
+ tags: [Data Products]
+ summary: List the caller's data products
+ operationId: listMyDataProducts
+ responses:
+ "200":
+ description: The datasets the caller owns.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/DataProductResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/data-products/shared-with-me:
+ get:
+ tags: [Data Products]
+ summary: List data products shared with the caller
+ description: Each entry carries `permission`, the caller's effective standing.
+ operationId: listDataProductsSharedWithMe
+ responses:
+ "200":
+ description: The datasets shared with the caller.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/DataProductResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/data-products/{dataProductId}:
+ parameters:
+ - $ref: "#/components/parameters/DataProductId"
+ get:
+ tags: [Data Products]
+ summary: Read a data product
+ description: Requires a `READ` standing.
+ operationId: getDataProduct
+ responses:
+ "200":
+ description: The dataset.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/DataProductResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ put:
+ tags: [Data Products]
+ summary: Update a data product
+ description: Requires a `WRITE` standing.
+ operationId: updateDataProduct
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/DataProductRequest" }
+ responses:
+ "200":
+ description: The updated dataset.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/DataProductResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Data Products]
+ summary: Delete a data product
+ description: Owner or admin.
+ operationId: deleteDataProduct
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "409": { $ref: "#/components/responses/Conflict" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/data-products/{dataProductId}/group-shares:
+ parameters:
+ - $ref: "#/components/parameters/DataProductId"
+ get:
+ tags: [Data Products]
+ summary: List a product's group shares
+ description: Owner or admin.
+ operationId: listDataProductGroupShares
+ responses:
+ "200":
+ description: The group shares on this dataset.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ {
+ $ref: "#/components/schemas/DataProductGroupSharingResponse",
+ }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Data Products]
+ summary: Share a product with a group
+ description: |
+ An omitted `permission` grants `READ` — read-only is the safe default for a
+ share, and widening it is a deliberate act.
+ operationId: shareDataProductWithGroup
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/DataProductGroupSharingRequest" }
+ responses:
+ "201":
+ description: The created share.
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/DataProductGroupSharingResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/data-products/{dataProductId}/group-shares/{sharingId}:
+ parameters:
+ - $ref: "#/components/parameters/DataProductId"
+ - $ref: "#/components/parameters/SharingId"
+ put:
+ tags: [Data Products]
+ summary: Change what a group share grants
+ operationId: updateDataProductGroupShare
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/DataProductSharingUpdate" }
+ responses:
+ "200":
+ description: The updated share.
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/DataProductGroupSharingResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Data Products]
+ summary: Revoke a group share
+ operationId: revokeDataProductGroupShare
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/data-products/{dataProductId}/user-shares:
+ parameters:
+ - $ref: "#/components/parameters/DataProductId"
+ get:
+ tags: [Data Products]
+ summary: List a product's user shares
+ description: Owner or admin.
+ operationId: listDataProductUserShares
+ responses:
+ "200":
+ description: The user shares on this dataset.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ {
+ $ref: "#/components/schemas/DataProductUserSharingResponse",
+ }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Data Products]
+ summary: Share a product with one user
+ description: An omitted `permission` grants `READ`.
+ operationId: shareDataProductWithUser
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/DataProductUserSharingRequest" }
+ responses:
+ "201":
+ description: The created share.
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/DataProductUserSharingResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/data-products/{dataProductId}/user-shares/{sharingId}:
+ parameters:
+ - $ref: "#/components/parameters/DataProductId"
+ - $ref: "#/components/parameters/SharingId"
+ put:
+ tags: [Data Products]
+ summary: Change what a user share grants
+ operationId: updateDataProductUserShare
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/DataProductSharingUpdate" }
+ responses:
+ "200":
+ description: The updated share.
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/DataProductUserSharingResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Data Products]
+ summary: Revoke a user share
+ operationId: revokeDataProductUserShare
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ # ------------------------------------------------------------ Processes
+
+ /api/v1/processes:
+ get:
+ tags: [Processes]
+ summary: List processes
+ description: |
+ The unfiltered listing is admin only. Supplying `deploymentId` narrows it to
+ the runs of one deployment, which is open to any authenticated caller.
+ operationId: listProcesses
+ parameters:
+ - name: deploymentId
+ in: query
+ required: false
+ description: Return only the runs of this deployment.
+ schema: { type: string }
+ responses:
+ "200":
+ description: The matching processes.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/ProcessResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Processes]
+ summary: Create a process
+ description: |
+ The owner comes from the token. A `BATCH_JOB` must carry a `batchProcess`
+ section; any other process type must not. The cluster config named there must
+ be one the caller owns or that has been shared with them.
+ operationId: createProcess
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/ProcessRequest" }
+ responses:
+ "201":
+ description: The created process.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/ProcessResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/processes/{processId}:
+ parameters:
+ - $ref: "#/components/parameters/ProcessId"
+ get:
+ tags: [Processes]
+ summary: Read a process
+ description: Owner or admin. Every section the process carries is nested inside it.
+ operationId: getProcess
+ responses:
+ "200":
+ description: The process.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/ProcessResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ put:
+ tags: [Processes]
+ summary: Update a process
+ description: |
+ Requires `ADMIN` or `SUPER_ADMIN` authority. The input and output mapping sets
+ are replaced wholesale.
+ operationId: updateProcess
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/ProcessRequest" }
+ responses:
+ "200":
+ description: The updated process.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/ProcessResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Processes]
+ summary: Delete a process
+ description: Requires `ADMIN` or `SUPER_ADMIN` authority.
+ operationId: deleteProcess
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "409": { $ref: "#/components/responses/Conflict" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/processes/{processId}/launch:
+ parameters:
+ - $ref: "#/components/parameters/ProcessId"
+ post:
+ tags: [Processes]
+ summary: Launch a process
+ description: |
+ Owner only. Takes no body. Answers `202`: the tasks are recorded here, and
+ carrying them out is later work done elsewhere. The body is the process
+ itself, so a caller that launched by id has the run to hand. What the launch
+ produced is read back through the process's task collections.
+ operationId: launchProcess
+ responses:
+ "202":
+ description: The launch was accepted; the process is returned as it now stands.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/ProcessResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "409": { $ref: "#/components/responses/Conflict" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/processes/{processId}/statuses:
+ parameters:
+ - $ref: "#/components/parameters/ProcessId"
+ get:
+ tags: [Processes]
+ summary: List a process's status history
+ description: |
+ Read-only. Statuses are recorded internally — by process submission and by
+ whatever submits and monitors the actual job — never accepted from a client,
+ so there is no `POST` or `PUT` here.
+ operationId: listProcessStatuses
+ responses:
+ "200":
+ description: Every status recorded for the process.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/StatusResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/processes/{processId}/statuses/{statusId}:
+ parameters:
+ - $ref: "#/components/parameters/ProcessId"
+ - name: statusId
+ in: path
+ required: true
+ schema: { type: string }
+ get:
+ tags: [Processes]
+ summary: Read one recorded status
+ operationId: getProcessStatus
+ responses:
+ "200":
+ description: The status.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/StatusResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/processes/{processId}/data-staging-tasks:
+ parameters:
+ - $ref: "#/components/parameters/ProcessId"
+ get:
+ tags: [Processes]
+ summary: List a process's data staging tasks
+ operationId: listDataStagingTasks
+ responses:
+ "200":
+ description: The process's staging tasks.
+ content:
+ application/json:
+ schema:
+ type: array
+ items: { $ref: "#/components/schemas/DataStagingTaskResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Processes]
+ summary: Create a data staging task
+ description: |
+ The task belongs to the process in the path; there is no `processId` in the
+ body, so a task cannot be moved to a different process by editing it.
+ operationId: createDataStagingTask
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/DataStagingTaskRequest" }
+ responses:
+ "201":
+ description: The created task.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/DataStagingTaskResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/processes/{processId}/data-staging-tasks/{taskId}:
+ parameters:
+ - $ref: "#/components/parameters/ProcessId"
+ - $ref: "#/components/parameters/TaskId"
+ get:
+ tags: [Processes]
+ summary: Read a data staging task
+ operationId: getDataStagingTask
+ responses:
+ "200":
+ description: The task.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/DataStagingTaskResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ put:
+ tags: [Processes]
+ summary: Update a data staging task
+ operationId: updateDataStagingTask
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/DataStagingTaskRequest" }
+ responses:
+ "200":
+ description: The updated task.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/DataStagingTaskResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Processes]
+ summary: Delete a data staging task
+ operationId: deleteDataStagingTask
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/processes/{processId}/job-submission-tasks:
+ parameters:
+ - $ref: "#/components/parameters/ProcessId"
+ get:
+ tags: [Processes]
+ summary: List a process's job submission tasks
+ operationId: listJobSubmissionTasks
+ responses:
+ "200":
+ description: The process's submission tasks.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ { $ref: "#/components/schemas/JobSubmissionTaskResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Processes]
+ summary: Create a job submission task
+ operationId: createJobSubmissionTask
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/JobSubmissionTaskRequest" }
+ responses:
+ "201":
+ description: The created task.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/JobSubmissionTaskResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/processes/{processId}/job-submission-tasks/{taskId}:
+ parameters:
+ - $ref: "#/components/parameters/ProcessId"
+ - $ref: "#/components/parameters/TaskId"
+ get:
+ tags: [Processes]
+ summary: Read a job submission task
+ operationId: getJobSubmissionTask
+ responses:
+ "200":
+ description: The task.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/JobSubmissionTaskResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ put:
+ tags: [Processes]
+ summary: Update a job submission task
+ operationId: updateJobSubmissionTask
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/JobSubmissionTaskRequest" }
+ responses:
+ "200":
+ description: The updated task.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/JobSubmissionTaskResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Processes]
+ summary: Delete a job submission task
+ operationId: deleteJobSubmissionTask
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/processes/{processId}/job-monitoring-tasks:
+ parameters:
+ - $ref: "#/components/parameters/ProcessId"
+ get:
+ tags: [Processes]
+ summary: List a process's job monitoring tasks
+ operationId: listJobMonitoringTasks
+ responses:
+ "200":
+ description: The process's monitoring tasks.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ { $ref: "#/components/schemas/JobMonitoringTaskResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Processes]
+ summary: Create a job monitoring task
+ operationId: createJobMonitoringTask
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/JobMonitoringTaskRequest" }
+ responses:
+ "201":
+ description: The created task.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/JobMonitoringTaskResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/processes/{processId}/job-monitoring-tasks/{taskId}:
+ parameters:
+ - $ref: "#/components/parameters/ProcessId"
+ - $ref: "#/components/parameters/TaskId"
+ get:
+ tags: [Processes]
+ summary: Read a job monitoring task
+ operationId: getJobMonitoringTask
+ responses:
+ "200":
+ description: The task.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/JobMonitoringTaskResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ put:
+ tags: [Processes]
+ summary: Update a job monitoring task
+ operationId: updateJobMonitoringTask
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/JobMonitoringTaskRequest" }
+ responses:
+ "200":
+ description: The updated task.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/JobMonitoringTaskResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Processes]
+ summary: Delete a job monitoring task
+ operationId: deleteJobMonitoringTask
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/processes/{processId}/interactive-command-tasks:
+ parameters:
+ - $ref: "#/components/parameters/ProcessId"
+ get:
+ tags: [Processes]
+ summary: List a process's interactive command tasks
+ operationId: listInteractiveCommandTasks
+ responses:
+ "200":
+ description: The process's command tasks.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ {
+ $ref: "#/components/schemas/InteractiveCommandTaskResponse",
+ }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ post:
+ tags: [Processes]
+ summary: Create an interactive command task
+ operationId: createInteractiveCommandTask
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/InteractiveCommandTaskRequest" }
+ responses:
+ "201":
+ description: The created task.
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/InteractiveCommandTaskResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+ /api/v1/processes/{processId}/interactive-command-tasks/{taskId}:
+ parameters:
+ - $ref: "#/components/parameters/ProcessId"
+ - $ref: "#/components/parameters/TaskId"
+ get:
+ tags: [Processes]
+ summary: Read an interactive command task
+ operationId: getInteractiveCommandTask
+ responses:
+ "200":
+ description: The task.
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/InteractiveCommandTaskResponse" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ put:
+ tags: [Processes]
+ summary: Update an interactive command task
+ operationId: updateInteractiveCommandTask
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/InteractiveCommandTaskRequest" }
+ responses:
+ "200":
+ description: The updated task.
+ content:
+ application/json:
+ schema:
+ { $ref: "#/components/schemas/InteractiveCommandTaskResponse" }
+ "400": { $ref: "#/components/responses/BadRequest" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+ delete:
+ tags: [Processes]
+ summary: Delete an interactive command task
+ operationId: deleteInteractiveCommandTask
+ responses:
+ "204": { $ref: "#/components/responses/NoContent" }
+ "401": { $ref: "#/components/responses/Unauthorized" }
+ "403": { $ref: "#/components/responses/Forbidden" }
+ "404": { $ref: "#/components/responses/NotFound" }
+ "502": { $ref: "#/components/responses/BadGateway" }
+ "500": { $ref: "#/components/responses/InternalError" }
+
+components:
+ securitySchemes:
+ bearerAuth:
+ type: http
+ scheme: bearer
+ description: |
+ A CILogon access token, validated by RFC 7662 introspection. The bootstrap
+ root token is also accepted; see INSTALL.md.
+
+ parameters:
+ UserId:
+ name: userId
+ in: path
+ required: true
+ schema: { type: string }
+ GroupId:
+ name: groupId
+ in: path
+ required: true
+ schema: { type: string }
+ SSHKeyId:
+ name: sshKeyId
+ in: path
+ required: true
+ schema: { type: string, format: uuid }
+ SlurmClusterId:
+ name: slurmClusterId
+ in: path
+ required: true
+ schema: { type: string, format: uuid }
+ PartitionId:
+ name: partitionId
+ in: path
+ required: true
+ schema: { type: string, format: uuid }
+ SlurmClusterConfigId:
+ name: slurmClusterConfigId
+ in: path
+ required: true
+ schema: { type: string, format: uuid }
+ TemplateId:
+ name: templateId
+ in: path
+ required: true
+ schema: { type: string, format: uuid }
+ DeploymentId:
+ name: deploymentId
+ in: path
+ required: true
+ schema: { type: string, format: uuid }
+ DataStorageId:
+ name: dataStorageId
+ in: path
+ required: true
+ schema: { type: string, format: uuid }
+ DataProductId:
+ name: dataProductId
+ in: path
+ required: true
+ schema: { type: string, format: uuid }
+ ProcessId:
+ name: processId
+ in: path
+ required: true
+ schema: { type: string, format: uuid }
+ TaskId:
+ name: taskId
+ in: path
+ required: true
+ schema: { type: string, format: uuid }
+ SharingId:
+ name: sharingId
+ in: path
+ required: true
+ schema: { type: string, format: uuid }
+
+ responses:
+ NoContent:
+ description: Deleted. No body.
+ BadRequest:
+ description: |
+ Malformed JSON, or a body that failed its constraints. Validation failures
+ carry `fieldErrors`.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/Error" }
+ examples:
+ validation:
+ summary: Validation failed
+ value:
+ status: 400
+ error: Bad Request
+ message: Validation failed
+ fieldErrors:
+ - field: clusterName
+ message: Cluster name cannot be blank
+ Unauthorized:
+ description: |
+ No token where one is required, or a token that is present but unusable. An
+ invalid token also carries `WWW-Authenticate: Bearer error="invalid_token"`.
+ headers:
+ WWW-Authenticate:
+ description: Present when the token was rejected.
+ schema: { type: string }
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/Error" }
+ Forbidden:
+ description: Authenticated, but lacking the authority for this call.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/Error" }
+ NotFound:
+ description: |
+ No such record — or, for a group, a record the caller has no standing to know
+ exists.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/Error" }
+ example:
+ status: 404
+ error: Not Found
+ message: "Slurm cluster not found: c1d2e3f4-5678-4abc-9def-0123456789ab"
+ Conflict:
+ description: |
+ The request collides with existing state — deleting an SSH key still in use,
+ or a template that still has deployments.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/Error" }
+ BadGateway:
+ description: |
+ The identity provider could not be reached to validate the token. Not the
+ caller's fault, so it is not reported as an auth failure.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/Error" }
+ InternalError:
+ description: |
+ An unexpected server failure. The message is always `"Internal server error"`;
+ the detail is logged rather than returned, since it may name internal state.
+
+ A `502` is also possible on any authenticated call if the identity provider is
+ unreachable.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/Error" }
+
+ schemas:
+ # ---------------------------------------------------------- Errors
+
+ FieldError:
+ type: object
+ description: One failed validation constraint.
+ properties:
+ field:
+ type: string
+ description: |
+ The JSON path of the offending field, including indexes and nesting.
+ examples:
+ ["inputs[0].inputType", "defaultBatchJobConfig.wallTimeMinutes"]
+ message: { type: string }
+ required: [field, message]
+
+ Error:
+ type: object
+ description: The envelope every failure response uses.
+ properties:
+ status:
+ type: integer
+ description: The HTTP status, repeated in the body.
+ error:
+ type: string
+ description: The standard reason phrase for `status`.
+ message:
+ type: string
+ description: Human-readable detail.
+ fieldErrors:
+ type: array
+ description: |
+ Present only on request-body validation failures, listing every
+ constraint that failed so a client can report all of them at once.
+ items: { $ref: "#/components/schemas/FieldError" }
+ required: [status, error, message]
+
+ # ----------------------------------------------------------- Enums
+
+ AuthMethod:
+ type: string
+ enum: [CILOGON, SYSTEM]
+ UserStatus:
+ type: string
+ enum: [ACTIVE, INACTIVE, SUSPENDED]
+ GroupRole:
+ type: string
+ enum: [ADMIN, MODERATOR, MEMBER]
+ GroupMemberStatus:
+ type: string
+ enum: [ACTIVE, INACTIVE]
+ ClusterPermission:
+ type: string
+ enum: [READ, WRITE]
+ DataStoragePermission:
+ type: string
+ enum: [READ, WRITE]
+ DataProductPermission:
+ type: string
+ enum: [READ, WRITE]
+ DataStorageType:
+ type: string
+ enum: [SCP, HPC]
+ ProvisionStatus:
+ type: string
+ description: |
+ The lifecycle state of a registered dataset. `REGISTERD` is spelled that way
+ on the wire.
+ enum:
+ [
+ REGISTERD,
+ PROVISIONING,
+ PROVISIONED,
+ DEPROVISIONING,
+ DEPROVISIONED,
+ FAILED,
+ ]
+ TemplateInputType:
+ type: string
+ enum: [STRING, INTEGER, FLOAT, BOOLEAN, FILE, FILE_LIST, DIRECTORY]
+ TemplateOutputType:
+ type: string
+ enum: [FILE, FILE_LIST, DIRECTORY]
+ ProcessType:
+ type: string
+ enum: [BATCH_JOB, CLOUD_JOB]
+ ProcessStatusType:
+ type: string
+ enum: [CREATED, SUBMITTED, RUNNING, COMPLETED, FAILED]
+ OnFailureAction:
+ type: string
+ enum: [RETRY, SKIP, EXIT]
+ BatchJobStatusType:
+ type: string
+ description: |
+ Airavata's own states (`SUBMITTED`, `SUBMISSION_FAILED`, `CANCELLING`,
+ `CANCELLED`) plus the Slurm mail states parsed from job notifications.
+ enum:
+ - SUBMITTED
+ - SUBMISSION_FAILED
+ - CANCELLING
+ - CANCELLED
+ - BEGIN
+ - REQUEUE
+ - INVALID_DEPEND
+ - STAGE_OUT
+ - TIME_LIMIT
+ - TIME_LIMIT_90
+ - TIME_LIMIT_80
+ - TIME_LIMIT_50
+ - FAIL
+ - END
+
+ # ------------------------------------------------------------ IAM
+
+ UserRegistration:
+ type: object
+ description: The create/update payload for a user.
+ properties:
+ userId: { type: string }
+ email: { type: [string, "null"], format: email }
+ firstName: { type: [string, "null"] }
+ lastName: { type: [string, "null"] }
+ authMethod:
+ oneOf:
+ - { $ref: "#/components/schemas/AuthMethod" }
+ - { type: "null" }
+ required: [userId]
+
+ UserResponse:
+ type: object
+ description: |
+ The read model for a user. It deliberately omits email, auth method and roles.
+ properties:
+ userId: { type: string }
+ status:
+ oneOf:
+ - { $ref: "#/components/schemas/UserStatus" }
+ - { type: "null" }
+ firstName: { type: [string, "null"] }
+ lastName: { type: [string, "null"] }
+ createdAt:
+ type: integer
+ format: int64
+ description: Milliseconds since the epoch.
+ required: [userId, status, firstName, lastName, createdAt]
+
+ GroupRequest:
+ type: object
+ description: |
+ The create/update payload for a group. There is no owner field: ownership
+ comes from the token and is immutable.
+ properties:
+ groupName: { type: string }
+ required: [groupName]
+
+ GroupResponse:
+ type: object
+ description: |
+ The read model for a group. Members are not inlined — read them through
+ `/api/v1/groups/{groupId}/members`, which keeps a large group's listing out of
+ every response that merely names it.
+ properties:
+ groupId: { type: string }
+ groupName: { type: [string, "null"] }
+ ownerId: { type: [string, "null"] }
+ createdAt: { type: integer, format: int64 }
+ required: [groupId, createdAt]
+
+ GroupMemberRequest:
+ type: object
+ description: |
+ Adds a user to a group. An omitted `groupRole` means `MEMBER`; an omitted
+ `groupMemberStatus` means `ACTIVE`.
+ properties:
+ userId: { type: string }
+ groupRole:
+ oneOf:
+ - { $ref: "#/components/schemas/GroupRole" }
+ - { type: "null" }
+ groupMemberStatus:
+ oneOf:
+ - { $ref: "#/components/schemas/GroupMemberStatus" }
+ - { type: "null" }
+ required: [userId]
+
+ GroupMemberUpdate:
+ type: object
+ description: |
+ Changes an existing membership. Both fields are optional and an omitted one is
+ left as it stands.
+ properties:
+ groupRole:
+ oneOf:
+ - { $ref: "#/components/schemas/GroupRole" }
+ - { type: "null" }
+ groupMemberStatus:
+ oneOf:
+ - { $ref: "#/components/schemas/GroupMemberStatus" }
+ - { type: "null" }
+
+ GroupMemberResponse:
+ type: object
+ properties:
+ groupId: { type: string }
+ userId: { type: string }
+ groupRole: { $ref: "#/components/schemas/GroupRole" }
+ groupMemberStatus: { $ref: "#/components/schemas/GroupMemberStatus" }
+ required: [groupId, userId, groupRole, groupMemberStatus]
+
+ # ---------------------------------------------------- Credentials
+
+ SSHKeyRequest:
+ type: object
+ description: |
+ The create/update payload for a key. `privateKey` is required on create; an
+ update that omits it keeps the stored secret. There is no owner field.
+ properties:
+ sshKeyName: { type: string }
+ publicKey: { type: string }
+ privateKey:
+ type: [string, "null"]
+ description: Write-only. Never returned by any read endpoint.
+ passphrase:
+ type: [string, "null"]
+ description: Write-only. Never returned by any read endpoint.
+ required: [sshKeyName, publicKey]
+
+ SSHKeyResponse:
+ type: object
+ description: |
+ The read model for a key. It has no private key or passphrase field at all —
+ the secrets cannot leak through this endpoint because there is nowhere for
+ them to go.
+ properties:
+ sshKeyId: { type: string, format: uuid }
+ sshKeyName: { type: string }
+ publicKey: { type: string }
+ ownerId: { type: string }
+ required: [sshKeyId, sshKeyName, publicKey, ownerId]
+
+ # -------------------------------------------------------- Compute
+
+ ClusterPartitionRequest:
+ type: object
+ properties:
+ name: { type: string }
+ description: { type: [string, "null"] }
+ maxRunTime: { type: [integer, "null"], format: int32 }
+ maxNodes: { type: [integer, "null"], format: int32 }
+ maxProcessors: { type: [integer, "null"], format: int32 }
+ maxJobsInQueue: { type: [integer, "null"], format: int32 }
+ maxMemory: { type: [integer, "null"], format: int64 }
+ cpuPerNode: { type: [integer, "null"], format: int32 }
+ defaultNodeCount: { type: [integer, "null"], format: int32 }
+ defaultCpuCount: { type: [integer, "null"], format: int32 }
+ defaultWalltime: { type: [integer, "null"], format: int64 }
+ gres: { type: [string, "null"] }
+ nodes: { type: [string, "null"] }
+ isDefaultQueue: { type: [boolean, "null"] }
+ isCheckpointable: { type: [boolean, "null"] }
+ required: [name]
+
+ ClusterPartitionResponse:
+ type: object
+ properties:
+ partitionId: { type: string, format: uuid }
+ clusterId: { type: [string, "null"], format: uuid }
+ name: { type: string }
+ description: { type: [string, "null"] }
+ maxRunTime: { type: [integer, "null"], format: int32 }
+ maxNodes: { type: [integer, "null"], format: int32 }
+ maxProcessors: { type: [integer, "null"], format: int32 }
+ maxJobsInQueue: { type: [integer, "null"], format: int32 }
+ maxMemory: { type: [integer, "null"], format: int64 }
+ cpuPerNode: { type: [integer, "null"], format: int32 }
+ defaultNodeCount: { type: [integer, "null"], format: int32 }
+ defaultCpuCount: { type: [integer, "null"], format: int32 }
+ defaultWalltime: { type: [integer, "null"], format: int64 }
+ gres: { type: [string, "null"] }
+ nodes: { type: [string, "null"] }
+ isDefaultQueue: { type: [boolean, "null"] }
+ isCheckpointable: { type: [boolean, "null"] }
+ required: [partitionId, name]
+
+ SlurmClusterRequest:
+ type: object
+ description: |
+ The create/update payload for a cluster. It describes the machine only.
+ properties:
+ clusterName: { type: string }
+ clusterDescription: { type: [string, "null"] }
+ headnodeHost: { type: string }
+ headnodePort: { type: integer, minimum: 1, maximum: 65535 }
+ slurmHome:
+ type: [string, "null"]
+ description: |
+ The install prefix the Slurm client commands are found under. A cluster
+ that omits it runs them off the login shell's `PATH`.
+ dataHost:
+ type: [string, "null"]
+ description: |
+ The optional separate endpoint for data movement. A cluster that names
+ neither `dataHost` nor `dataPort` stages through its head node.
+ dataPort: { type: [integer, "null"], minimum: 1, maximum: 65535 }
+ partitions:
+ type: [array, "null"]
+ description: |
+ Accepted on create only, so a cluster whose layout is already known
+ arrives complete. An update carrying them is rejected.
+ items: { $ref: "#/components/schemas/ClusterPartitionRequest" }
+ required: [clusterName, headnodeHost, headnodePort]
+
+ SlurmClusterResponse:
+ type: object
+ properties:
+ slurmClusterId: { type: string, format: uuid }
+ clusterName: { type: string }
+ clusterDescription: { type: [string, "null"] }
+ headnodeHost: { type: string }
+ headnodePort: { type: integer }
+ slurmHome: { type: [string, "null"] }
+ dataHost: { type: [string, "null"] }
+ dataPort: { type: [integer, "null"] }
+ partitions:
+ type: array
+ items: { $ref: "#/components/schemas/ClusterPartitionResponse" }
+ required:
+ [slurmClusterId, clusterName, headnodeHost, headnodePort, partitions]
+
+ SlurmClusterConfigRequest:
+ type: object
+ description: |
+ The create/update payload for a cluster login config. There is no owner field.
+ properties:
+ name: { type: [string, "null"] }
+ description: { type: [string, "null"] }
+ slurmClusterId: { type: string, format: uuid }
+ loginUser: { type: string }
+ workRoot: { type: string }
+ sshKeyId:
+ type: string
+ format: uuid
+ description: Must be a key the caller owns.
+ required: [slurmClusterId, loginUser, workRoot, sshKeyId]
+
+ SlurmClusterConfigResponse:
+ type: object
+ description: |
+ The cluster is inlined because a config is only meaningful together with the
+ machine it logs in to. The key is inlined as its safe summary.
+ properties:
+ slurmClusterConfigId: { type: string, format: uuid }
+ name: { type: [string, "null"] }
+ description: { type: [string, "null"] }
+ ownerId: { type: [string, "null"] }
+ slurmClusterId: { type: string, format: uuid }
+ slurmCluster:
+ oneOf:
+ - { $ref: "#/components/schemas/SlurmClusterResponse" }
+ - { type: "null" }
+ loginUser: { type: string }
+ workRoot: { type: string }
+ sshKeyId: { type: [string, "null"], format: uuid }
+ sshKey:
+ oneOf:
+ - { $ref: "#/components/schemas/SSHKeyResponse" }
+ - { type: "null" }
+ permission:
+ type: string
+ description: |
+ The caller's effective standing, present only on the listings that compute
+ it (`/shared-with-me`).
+ enum: [READ, WRITE]
+ required: [slurmClusterConfigId, slurmClusterId, loginUser, workRoot]
+
+ SlurmClusterConfigGroupSharingRequest:
+ type: object
+ properties:
+ groupId: { type: string }
+ permission:
+ oneOf:
+ - { $ref: "#/components/schemas/ClusterPermission" }
+ - { type: "null" }
+ required: [groupId]
+
+ SlurmClusterConfigUserSharingRequest:
+ type: object
+ properties:
+ userId: { type: string }
+ permission:
+ oneOf:
+ - { $ref: "#/components/schemas/ClusterPermission" }
+ - { type: "null" }
+ required: [userId]
+
+ SlurmClusterConfigSharingUpdate:
+ type: object
+ properties:
+ permission: { $ref: "#/components/schemas/ClusterPermission" }
+ required: [permission]
+
+ SlurmClusterConfigGroupSharingResponse:
+ type: object
+ properties:
+ slurmClusterConfigGroupSharingId: { type: string, format: uuid }
+ slurmClusterConfigId: { type: string, format: uuid }
+ groupId: { type: string }
+ permission: { $ref: "#/components/schemas/ClusterPermission" }
+ required:
+ [
+ slurmClusterConfigGroupSharingId,
+ slurmClusterConfigId,
+ groupId,
+ permission,
+ ]
+
+ SlurmClusterConfigUserSharingResponse:
+ type: object
+ properties:
+ slurmClusterConfigUserSharingId: { type: string, format: uuid }
+ slurmClusterConfigId: { type: string, format: uuid }
+ userId: { type: string }
+ permission: { $ref: "#/components/schemas/ClusterPermission" }
+ required:
+ [
+ slurmClusterConfigUserSharingId,
+ slurmClusterConfigId,
+ userId,
+ permission,
+ ]
+
+ # ---------------------------------------------------- Application
+
+ TemplateInput:
+ type: object
+ description: |
+ One declared input, used for both reads and writes. `inputId` is echoed back
+ on reads but ignored on writes.
+ properties:
+ inputId: { type: string, format: uuid }
+ inputName: { type: string }
+ displayName: { type: [string, "null"] }
+ inputDescription: { type: [string, "null"] }
+ inputType: { $ref: "#/components/schemas/TemplateInputType" }
+ required:
+ type: boolean
+ description: |
+ Spelled `required` on the wire, not `isRequired`.
+ required: [inputName, inputType]
+
+ TemplateOutput:
+ type: object
+ properties:
+ outputId: { type: string, format: uuid }
+ outputName: { type: string }
+ displayName: { type: [string, "null"] }
+ outputDescription: { type: [string, "null"] }
+ outputType: { $ref: "#/components/schemas/TemplateOutputType" }
+ required: [outputName, outputType]
+
+ TemplateRequest:
+ type: object
+ properties:
+ templateName: { type: string }
+ templateDescription: { type: [string, "null"] }
+ inputs:
+ type: [array, "null"]
+ description: Input names must be unique within a template.
+ items: { $ref: "#/components/schemas/TemplateInput" }
+ outputs:
+ type: [array, "null"]
+ items: { $ref: "#/components/schemas/TemplateOutput" }
+ required: [templateName]
+
+ TemplateResponse:
+ type: object
+ properties:
+ templateId: { type: string, format: uuid }
+ templateName: { type: [string, "null"] }
+ templateDescription: { type: [string, "null"] }
+ inputs:
+ type: array
+ items: { $ref: "#/components/schemas/TemplateInput" }
+ outputs:
+ type: array
+ items: { $ref: "#/components/schemas/TemplateOutput" }
+ required: [templateId, inputs, outputs]
+
+ BatchJobConfigRequest:
+ type: object
+ description: The resource request carried by a deployment or a run.
+ properties:
+ wallTimeMinutes: { type: integer, format: int64, exclusiveMinimum: 0 }
+ allocation: { type: string }
+ cpus: { type: [integer, "null"], format: int32 }
+ mem: { type: [string, "null"] }
+ memPerCpu: { type: [string, "null"] }
+ ntasksPerNode: { type: [integer, "null"], format: int32 }
+ cpusPerTask: { type: [integer, "null"], format: int32 }
+ nodes: { type: [integer, "null"], format: int32 }
+ ntasks: { type: [integer, "null"], format: int32 }
+ gres: { type: [string, "null"] }
+ gpus: { type: [integer, "null"], format: int32 }
+ memPerGpu: { type: [string, "null"] }
+ cpusPerGpu: { type: [string, "null"] }
+ gpusPerNode: { type: [integer, "null"], format: int32 }
+ constraints: { type: [string, "null"] }
+ required: [wallTimeMinutes, allocation]
+
+ BatchJobConfigResponse:
+ type: object
+ properties:
+ batchJobConfigId: { type: string, format: uuid }
+ wallTimeMinutes: { type: integer, format: int64 }
+ allocation: { type: string }
+ cpus: { type: [integer, "null"], format: int32 }
+ mem: { type: [string, "null"] }
+ memPerCpu: { type: [string, "null"] }
+ ntasksPerNode: { type: [integer, "null"], format: int32 }
+ cpusPerTask: { type: [integer, "null"], format: int32 }
+ nodes: { type: [integer, "null"], format: int32 }
+ ntasks: { type: [integer, "null"], format: int32 }
+ gres: { type: [string, "null"] }
+ gpus: { type: [integer, "null"], format: int32 }
+ memPerGpu: { type: [string, "null"] }
+ cpusPerGpu: { type: [string, "null"] }
+ gpusPerNode: { type: [integer, "null"], format: int32 }
+ constraints: { type: [string, "null"] }
+ required: [batchJobConfigId, wallTimeMinutes, allocation]
+
+ BatchDeploymentRequest:
+ type: object
+ properties:
+ templateId: { type: string, format: uuid }
+ slurmClusterId: { type: [string, "null"], format: uuid }
+ slurmRunSection:
+ type: string
+ description: The run script section a job of this deployment executes.
+ defaultBatchJobConfig:
+ allOf: [{ $ref: "#/components/schemas/BatchJobConfigRequest" }]
+ description: |
+ The resource request a run of this deployment starts from. A run carries
+ its own copy and may depart from it.
+ defaultPartition: { type: [string, "null"] }
+ required: [templateId, slurmRunSection, defaultBatchJobConfig]
+
+ BatchDeploymentResponse:
+ type: object
+ properties:
+ deploymentId: { type: string, format: uuid }
+ templateId: { type: [string, "null"], format: uuid }
+ slurmClusterId: { type: [string, "null"], format: uuid }
+ slurmRunSection: { type: string }
+ defaultBatchJobConfig:
+ oneOf:
+ - { $ref: "#/components/schemas/BatchJobConfigResponse" }
+ - { type: "null" }
+ defaultPartition: { type: [string, "null"] }
+ required: [deploymentId, slurmRunSection]
+
+ # ----------------------------------------------------------- Data
+
+ SCPDataStorageRequest:
+ type: object
+ description: |
+ The create/update payload for a storage. There is no owner field.
+ properties:
+ dataName: { type: string }
+ hostName: { type: string }
+ port:
+ type: [integer, "null"]
+ minimum: 1
+ maximum: 65535
+ default: 22
+ loginUser: { type: string }
+ sshKeyId:
+ type: string
+ format: uuid
+ description: Must be a key the caller owns.
+ required: [dataName, hostName, loginUser, sshKeyId]
+
+ SCPDataStorageResponse:
+ type: object
+ properties:
+ dataId: { type: string, format: uuid }
+ dataName: { type: [string, "null"] }
+ ownerId: { type: [string, "null"] }
+ hostName: { type: [string, "null"] }
+ port: { type: [integer, "null"] }
+ loginUser: { type: [string, "null"] }
+ sshKeyId: { type: [string, "null"], format: uuid }
+ sshKey:
+ oneOf:
+ - { $ref: "#/components/schemas/SSHKeyResponse" }
+ - { type: "null" }
+ permission:
+ type: string
+ description: |
+ The caller's effective standing, present only on the listings that compute
+ it (`/shared-with-me`).
+ enum: [READ, WRITE]
+ required: [dataId]
+
+ SCPDataStorageGroupSharingRequest:
+ type: object
+ properties:
+ groupId: { type: string }
+ permission:
+ oneOf:
+ - { $ref: "#/components/schemas/DataStoragePermission" }
+ - { type: "null" }
+ required: [groupId]
+
+ SCPDataStorageUserSharingRequest:
+ type: object
+ properties:
+ userId: { type: string }
+ permission:
+ oneOf:
+ - { $ref: "#/components/schemas/DataStoragePermission" }
+ - { type: "null" }
+ required: [userId]
+
+ SCPDataStorageSharingUpdate:
+ type: object
+ properties:
+ permission: { $ref: "#/components/schemas/DataStoragePermission" }
+ required: [permission]
+
+ SCPDataStorageGroupSharingResponse:
+ type: object
+ properties:
+ dataStorageGroupSharingId: { type: string, format: uuid }
+ dataStorageId: { type: [string, "null"], format: uuid }
+ groupId: { type: [string, "null"] }
+ permission:
+ oneOf:
+ - { $ref: "#/components/schemas/DataStoragePermission" }
+ - { type: "null" }
+ required: [dataStorageGroupSharingId]
+
+ SCPDataStorageUserSharingResponse:
+ type: object
+ properties:
+ dataStorageUserSharingId: { type: string, format: uuid }
+ dataStorageId: { type: [string, "null"], format: uuid }
+ userId: { type: [string, "null"] }
+ permission:
+ oneOf:
+ - { $ref: "#/components/schemas/DataStoragePermission" }
+ - { type: "null" }
+ required: [dataStorageUserSharingId]
+
+ DataProductRequest:
+ type: object
+ description: |
+ The create/update payload for a registered dataset. There is no owner field
+ and no provision status: a caller who could set either would be able to
+ register a dataset as someone else's, or claim it was already provisioned.
+ properties:
+ dataName: { type: string }
+ dataDescription: { type: [string, "null"] }
+ isFile: { type: boolean }
+ path: { type: string }
+ dataStorageId: { type: string, format: uuid }
+ dataStorageType:
+ description: Defaults to `SCP`, the only kind `dataStorageId` resolves against.
+ oneOf:
+ - { $ref: "#/components/schemas/DataStorageType" }
+ - { type: "null" }
+ required: [dataName, isFile, path, dataStorageId]
+
+ DataProductResponse:
+ type: object
+ properties:
+ dataId: { type: string, format: uuid }
+ dataName: { type: [string, "null"] }
+ dataDescription: { type: [string, "null"] }
+ isFile: { type: boolean }
+ path: { type: [string, "null"] }
+ provisionStatus:
+ oneOf:
+ - { $ref: "#/components/schemas/ProvisionStatus" }
+ - { type: "null" }
+ ownerId: { type: [string, "null"] }
+ dataStorageId: { type: [string, "null"], format: uuid }
+ dataStorageType: { $ref: "#/components/schemas/DataStorageType" }
+ createdAt: { type: integer, format: int64 }
+ permission:
+ type: string
+ description: |
+ What the calling principal may do with this dataset — a property of the
+ request rather than of the record, which is why it is not stored. Present
+ only on the listings that compute it.
+ enum: [READ, WRITE]
+ required: [dataId, isFile, dataStorageType, createdAt]
+
+ DataProductGroupSharingRequest:
+ type: object
+ properties:
+ groupId: { type: string }
+ permission:
+ oneOf:
+ - { $ref: "#/components/schemas/DataProductPermission" }
+ - { type: "null" }
+ required: [groupId]
+
+ DataProductUserSharingRequest:
+ type: object
+ properties:
+ userId: { type: string }
+ permission:
+ oneOf:
+ - { $ref: "#/components/schemas/DataProductPermission" }
+ - { type: "null" }
+ required: [userId]
+
+ DataProductSharingUpdate:
+ type: object
+ description: |
+ The subject is fixed at creation; only the permission is editable.
+ properties:
+ permission: { $ref: "#/components/schemas/DataProductPermission" }
+ required: [permission]
+
+ DataProductGroupSharingResponse:
+ type: object
+ properties:
+ dataProductGroupSharingId: { type: string, format: uuid }
+ dataProductId: { type: [string, "null"], format: uuid }
+ groupId: { type: [string, "null"] }
+ permission:
+ oneOf:
+ - { $ref: "#/components/schemas/DataProductPermission" }
+ - { type: "null" }
+ required: [dataProductGroupSharingId]
+
+ DataProductUserSharingResponse:
+ type: object
+ properties:
+ dataProductUserSharingId: { type: string, format: uuid }
+ dataProductId: { type: [string, "null"], format: uuid }
+ userId: { type: [string, "null"] }
+ permission:
+ oneOf:
+ - { $ref: "#/components/schemas/DataProductPermission" }
+ - { type: "null" }
+ required: [dataProductUserSharingId]
+
+ # -------------------------------------------------------- Process
+
+ InputMapping:
+ type: object
+ description: |
+ Binds one of the deployment template's declared inputs to a value for this
+ run. `templateInputMappingId` is echoed back on reads but ignored on writes:
+ an update replaces the mapping set wholesale.
+ properties:
+ templateInputMappingId: { type: string, format: uuid }
+ templateInputId: { type: string, format: uuid }
+ value:
+ type: [string, "null"]
+ description: |
+ A JSON document, either `{"value": "..."}` for a single value or
+ `{"values": [...]}` for a list.
+ required: [templateInputId]
+
+ OutputMapping:
+ type: object
+ properties:
+ templateOutputMappingId: { type: string, format: uuid }
+ templateOutputId: { type: string, format: uuid }
+ value:
+ type: [string, "null"]
+ description: |
+ A JSON document, either `{"value": "..."}` for a single value or
+ `{"values": [...]}` for a list.
+ required: [templateOutputId]
+
+ BatchJobStatusResponse:
+ type: object
+ properties:
+ batchProcessStatusId: { type: string, format: uuid }
+ batchProcessId: { type: string, format: uuid }
+ status: { $ref: "#/components/schemas/BatchJobStatusType" }
+ updatedAt:
+ type: integer
+ format: int64
+ description: When the state was recorded, in milliseconds since the epoch.
+ required: [batchProcessStatusId, batchProcessId, status, updatedAt]
+
+ BatchProcessRequest:
+ type: object
+ description: |
+ The `batchProcess` section of a process request. The resource request is
+ carried here rather than copied from the deployment's default, which is what
+ lets a caller ask for different resources for a particular run.
+ properties:
+ deploymentId: { type: string, format: uuid }
+ jobId:
+ type: [string, "null"]
+ description: |
+ The scheduler's identifier for the submitted job — writable rather than
+ server-generated, because it is learned at submission time and recorded
+ afterwards.
+ batchJobConfig: { $ref: "#/components/schemas/BatchJobConfigRequest" }
+ slurmClusterConfigId:
+ type: string
+ format: uuid
+ description: |
+ The cluster login config this run submits under. Required: a deployment
+ names the machine but nobody's identity on it. Only a config the caller
+ owns or that is shared with them is accepted.
+ baseWorkDir:
+ type: [string, "null"]
+ description: |
+ The parent directory on the cluster this run works under; it gets a
+ subdirectory of its own beneath it, named for the process id, which is
+ where file inputs are staged to and file outputs staged from.
+ inputMappings:
+ type: [array, "null"]
+ items: { $ref: "#/components/schemas/InputMapping" }
+ outputMappings:
+ type: [array, "null"]
+ items: { $ref: "#/components/schemas/OutputMapping" }
+ required: [deploymentId, slurmClusterConfigId, batchJobConfig]
+
+ BatchProcessResponse:
+ type: object
+ description: |
+ The `batchProcess` section of a process response. The nested config is a
+ snapshot of what this run actually asked for.
+ properties:
+ batchProcessId: { type: string, format: uuid }
+ deploymentId: { type: [string, "null"], format: uuid }
+ jobId: { type: [string, "null"] }
+ batchJobConfig:
+ oneOf:
+ - { $ref: "#/components/schemas/BatchJobConfigResponse" }
+ - { type: "null" }
+ slurmClusterConfigId: { type: string, format: uuid }
+ baseWorkDir: { type: [string, "null"] }
+ inputMappings:
+ type: array
+ items: { $ref: "#/components/schemas/InputMapping" }
+ outputMappings:
+ type: array
+ items: { $ref: "#/components/schemas/OutputMapping" }
+ batchJobStatuses:
+ type: array
+ description: Oldest first.
+ items: { $ref: "#/components/schemas/BatchJobStatusResponse" }
+ latestBatchJobStatus:
+ oneOf:
+ - { $ref: "#/components/schemas/BatchJobStatusResponse" }
+ - { type: "null" }
+ required:
+ - batchProcessId
+ - slurmClusterConfigId
+ - inputMappings
+ - outputMappings
+ - batchJobStatuses
+
+ ProcessRequest:
+ type: object
+ description: |
+ The create/update payload for a process. There is no owner field. Which
+ sections a body may carry follows from its type: a `BATCH_JOB` without a
+ `batchProcess` describes no job at all, and a `batchProcess` on any other kind
+ is a caller's mistake rather than something to store and ignore.
+ properties:
+ processType: { $ref: "#/components/schemas/ProcessType" }
+ batchProcess:
+ description: Required when `processType` is `BATCH_JOB`; rejected otherwise.
+ oneOf:
+ - { $ref: "#/components/schemas/BatchProcessRequest" }
+ - { type: "null" }
+ required: [processType]
+
+ ProcessResponse:
+ type: object
+ description: |
+ The read model for a process, with every section it carries nested inside it.
+ properties:
+ processId: { type: string, format: uuid }
+ userId:
+ type: [string, "null"]
+ description: The owner, taken from the token at creation.
+ processType:
+ oneOf:
+ - { $ref: "#/components/schemas/ProcessType" }
+ - { type: "null" }
+ lastStatusId: { type: [string, "null"], format: uuid }
+ batchProcess:
+ oneOf:
+ - { $ref: "#/components/schemas/BatchProcessResponse" }
+ - { type: "null" }
+ required: [processId]
+
+ StatusResponse:
+ type: object
+ description: |
+ One recorded process status. There is no request counterpart: statuses are
+ never created or updated from a client body.
+ properties:
+ processStatusId: { type: string, format: uuid }
+ processId: { type: [string, "null"], format: uuid }
+ status:
+ oneOf:
+ - { $ref: "#/components/schemas/ProcessStatusType" }
+ - { type: "null" }
+ log: { type: [string, "null"] }
+ timestamp:
+ type: [integer, "null"]
+ format: int64
+ description: When the status was recorded, in milliseconds since the epoch.
+ required: [processStatusId]
+
+ DataStagingTaskRequest:
+ type: object
+ description: |
+ Both paths are required: a staging task that names neither where data comes
+ from nor where it goes describes no transfer at all.
+ properties:
+ sourceDataStorageId: { type: [string, "null"], format: uuid }
+ sourceDataStorageType:
+ oneOf:
+ - { $ref: "#/components/schemas/DataStorageType" }
+ - { type: "null" }
+ destinationDataStorageId: { type: [string, "null"], format: uuid }
+ destinationDataStorageType:
+ oneOf:
+ - { $ref: "#/components/schemas/DataStorageType" }
+ - { type: "null" }
+ sourcePath: { type: string }
+ destinationPath: { type: string }
+ onFailure:
+ oneOf:
+ - { $ref: "#/components/schemas/OnFailureAction" }
+ - { type: "null" }
+ retryCount: { type: [integer, "null"], minimum: 0 }
+ taskOrder: { type: [integer, "null"], minimum: 0 }
+ required: [sourcePath, destinationPath]
+
+ DataStagingTaskResponse:
+ type: object
+ properties:
+ taskId: { type: string, format: uuid }
+ processId: { type: [string, "null"], format: uuid }
+ sourceDataStorageId: { type: [string, "null"], format: uuid }
+ sourceDataStorageType:
+ oneOf:
+ - { $ref: "#/components/schemas/DataStorageType" }
+ - { type: "null" }
+ destinationDataStorageId: { type: [string, "null"], format: uuid }
+ destinationDataStorageType:
+ oneOf:
+ - { $ref: "#/components/schemas/DataStorageType" }
+ - { type: "null" }
+ sourcePath: { type: [string, "null"] }
+ destinationPath: { type: [string, "null"] }
+ onFailure:
+ oneOf:
+ - { $ref: "#/components/schemas/OnFailureAction" }
+ - { type: "null" }
+ retryCount: { type: [integer, "null"] }
+ taskOrder: { type: [integer, "null"] }
+ required: [taskId]
+
+ JobSubmissionTaskRequest:
+ type: object
+ properties:
+ jobId:
+ type: [string, "null"]
+ description: |
+ The scheduler's identifier for the submitted job — writable rather than
+ server-generated.
+ workingDir: { type: [string, "null"] }
+ onFailure:
+ oneOf:
+ - { $ref: "#/components/schemas/OnFailureAction" }
+ - { type: "null" }
+ retryCount: { type: [integer, "null"], minimum: 0 }
+ taskOrder: { type: [integer, "null"], minimum: 0 }
+
+ JobSubmissionTaskResponse:
+ type: object
+ properties:
+ taskId: { type: string, format: uuid }
+ processId: { type: [string, "null"], format: uuid }
+ jobId: { type: [string, "null"] }
+ workingDir: { type: [string, "null"] }
+ onFailure:
+ oneOf:
+ - { $ref: "#/components/schemas/OnFailureAction" }
+ - { type: "null" }
+ retryCount: { type: [integer, "null"] }
+ taskOrder: { type: [integer, "null"] }
+ required: [taskId]
+
+ JobMonitoringTaskRequest:
+ type: object
+ properties:
+ jobId: { type: [string, "null"] }
+ onFailure:
+ oneOf:
+ - { $ref: "#/components/schemas/OnFailureAction" }
+ - { type: "null" }
+ retryCount: { type: [integer, "null"], minimum: 0 }
+ taskOrder: { type: [integer, "null"], minimum: 0 }
+
+ JobMonitoringTaskResponse:
+ type: object
+ properties:
+ taskId: { type: string, format: uuid }
+ processId: { type: [string, "null"], format: uuid }
+ jobId: { type: [string, "null"] }
+ onFailure:
+ oneOf:
+ - { $ref: "#/components/schemas/OnFailureAction" }
+ - { type: "null" }
+ retryCount: { type: [integer, "null"] }
+ taskOrder: { type: [integer, "null"] }
+ required: [taskId]
+
+ InteractiveCommandTaskRequest:
+ type: object
+ properties:
+ command: { type: string }
+ output:
+ type: [string, "null"]
+ description: |
+ The result of running the command — writable for the same reason `jobId`
+ is, recorded once whatever executed it knows the answer.
+ onFailure:
+ oneOf:
+ - { $ref: "#/components/schemas/OnFailureAction" }
+ - { type: "null" }
+ retryCount: { type: [integer, "null"], minimum: 0 }
+ taskOrder: { type: [integer, "null"], minimum: 0 }
+ required: [command]
+
+ InteractiveCommandTaskResponse:
+ type: object
+ properties:
+ taskId: { type: string, format: uuid }
+ processId: { type: [string, "null"], format: uuid }
+ command: { type: [string, "null"] }
+ output: { type: [string, "null"] }
+ onFailure:
+ oneOf:
+ - { $ref: "#/components/schemas/OnFailureAction" }
+ - { type: "null" }
+ retryCount: { type: [integer, "null"] }
+ taskOrder: { type: [integer, "null"] }
+ required: [taskId]