diff --git a/apps/agora/api-docs/.env.example b/apps/agora/api-docs/.env.example
new file mode 100644
index 0000000000..b5c290eddd
--- /dev/null
+++ b/apps/agora/api-docs/.env.example
@@ -0,0 +1 @@
+PORT=8010
\ No newline at end of file
diff --git a/apps/agora/api-docs/Dockerfile b/apps/agora/api-docs/Dockerfile
new file mode 100644
index 0000000000..45838db32c
--- /dev/null
+++ b/apps/agora/api-docs/Dockerfile
@@ -0,0 +1,9 @@
+FROM redocly/redoc:v2.0.0
+
+HEALTHCHECK --interval=2s --timeout=3s --retries=5 --start-period=2s \
+ CMD curl --fail --silent "localhost:8010" || exit 1
+
+COPY build/redoc-static.html /usr/share/nginx/html/index.html
+COPY favicon.ico /usr/share/nginx/html/
+
+EXPOSE 8010
\ No newline at end of file
diff --git a/apps/agora/api-docs/favicon.ico b/apps/agora/api-docs/favicon.ico
new file mode 100644
index 0000000000..579161c11f
Binary files /dev/null and b/apps/agora/api-docs/favicon.ico differ
diff --git a/apps/agora/api-docs/index.hbs b/apps/agora/api-docs/index.hbs
new file mode 100644
index 0000000000..2adf572d49
--- /dev/null
+++ b/apps/agora/api-docs/index.hbs
@@ -0,0 +1,20 @@
+
+
+
+ {{title}}
+
+ {{!-- --}}
+
+
+ {{{redocHead}}}
+ {{#unless disableGoogleFont}}{{/unless}}
+
+
+ {{{redocHTML}}}
+
+
\ No newline at end of file
diff --git a/apps/agora/api-docs/project.json b/apps/agora/api-docs/project.json
new file mode 100644
index 0000000000..d647e8ac1e
--- /dev/null
+++ b/apps/agora/api-docs/project.json
@@ -0,0 +1,87 @@
+{
+ "name": "agora-api-docs",
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
+ "projectType": "application",
+ "targets": {
+ "create-config": {
+ "executor": "nx:run-commands",
+ "options": {
+ "command": "cp -n .env.example .env",
+ "cwd": "{projectRoot}"
+ }
+ },
+ "build": {
+ "executor": "nx:run-commands",
+ "options": {
+ "command": "redocly build-docs --config redocly.yaml --template index.hbs --output build/redoc-static.html",
+ "cwd": "{projectRoot}"
+ }
+ },
+ "serve": {
+ "executor": "nx:run-commands",
+ "options": {
+ "command": "redocly preview-docs --config redocly.yaml --port 8010",
+ "cwd": "{projectRoot}"
+ }
+ },
+ "serve-detach": {
+ "executor": "nx:run-commands",
+ "options": {
+ "command": "docker/agora/serve-detach.sh agora-api-docs"
+ }
+ },
+ "build-image": {
+ "executor": "@nx-tools/nx-container:build",
+ "options": {
+ "context": "apps/agora/api-docs",
+ "metadata": {
+ "images": [
+ "ghcr.io/sage-bionetworks/agora-api-docs"
+ ],
+ "tags": [
+ "type=edge,branch=main",
+ "type=raw,value=local",
+ "type=sha"
+ ]
+ },
+ "push": false
+ },
+ "dependsOn": [
+ "build"
+ ]
+ },
+ "publish-image": {
+ "executor": "@nx-tools/nx-container:build",
+ "options": {
+ "context": "apps/agora/api-docs",
+ "metadata": {
+ "images": [
+ "ghcr.io/sage-bionetworks/agora-api-docs"
+ ],
+ "tags": [
+ "type=edge,branch=main",
+ "type=sha"
+ ]
+ },
+ "push": true
+ },
+ "dependsOn": [
+ "build-image"
+ ]
+ },
+ "scan-image": {
+ "executor": "nx:run-commands",
+ "options": {
+ "command": "trivy image ghcr.io/sage-bionetworks/agora-api-docs:local --quiet",
+ "color": true
+ }
+ }
+ },
+ "tags": [
+ "type:docs",
+ "scope:backend"
+ ],
+ "implicitDependencies": [
+ "agora-api-description"
+ ]
+}
\ No newline at end of file
diff --git a/apps/agora/api-docs/redocly.yaml b/apps/agora/api-docs/redocly.yaml
new file mode 100644
index 0000000000..d8651d7af6
--- /dev/null
+++ b/apps/agora/api-docs/redocly.yaml
@@ -0,0 +1,11 @@
+# See https://docs.redoc.ly/cli/configuration/ for more information.
+apis:
+ main:
+ root: '../../../libs/agora/api-description/build/openapi.yaml'
+# theme:
+# openapi:
+# theme:
+# rightPanel:
+# backgroundColor: '#314fa7'
+# sidebar:
+# backgroundColor: '#ffffff'
diff --git a/docker/agora/serve-detach.sh b/docker/agora/serve-detach.sh
index 6afce4478b..978e2256f4 100755
--- a/docker/agora/serve-detach.sh
+++ b/docker/agora/serve-detach.sh
@@ -2,6 +2,7 @@
args=(
# List of services in alphanumeric order
+ --file docker/agora/services/api-docs.yml
--file docker/agora/services/app.yml
--file docker/agora/services/data.yml
--file docker/agora/services/mongo.yml
diff --git a/docker/agora/services/api-docs.yml b/docker/agora/services/api-docs.yml
new file mode 100644
index 0000000000..1882f7ab9a
--- /dev/null
+++ b/docker/agora/services/api-docs.yml
@@ -0,0 +1,15 @@
+services:
+ agora-api-docs:
+ image: ghcr.io/sage-bionetworks/agora-api-docs:${AGORA_VERSION:-local}
+ container_name: agora-api-docs
+ restart: always
+ env_file:
+ - ../../../apps/agora/api-docs/.env
+ networks:
+ - agora
+ ports:
+ - '8010:8010'
+ deploy:
+ resources:
+ limits:
+ memory: 200M
diff --git a/libs/agora/api-description/.gitignore b/libs/agora/api-description/.gitignore
new file mode 100644
index 0000000000..684ae5d80c
--- /dev/null
+++ b/libs/agora/api-description/.gitignore
@@ -0,0 +1 @@
+!build
\ No newline at end of file
diff --git a/libs/agora/api-description/README.md b/libs/agora/api-description/README.md
new file mode 100644
index 0000000000..08efae00a6
--- /dev/null
+++ b/libs/agora/api-description/README.md
@@ -0,0 +1,7 @@
+# api-spec
+
+This library was generated with [Nx](https://nx.dev).
+
+## Running unit tests
+
+Run `nx test api-spec` to execute the unit tests.
diff --git a/libs/agora/api-description/build/openapi.yaml b/libs/agora/api-description/build/openapi.yaml
new file mode 100644
index 0000000000..58a1b01753
--- /dev/null
+++ b/libs/agora/api-description/build/openapi.yaml
@@ -0,0 +1,87 @@
+openapi: 3.0.3
+info:
+ version: 1.0.0
+ title: Agora REST API
+ license:
+ name: Apache 2.0
+ url: https://github.com/Sage-Bionetworks/sage-monorepo/blob/main/LICENSE.txt
+ contact:
+ name: Support
+ url: https://github.com/Sage-Bionetworks/sage-monorepo
+ x-logo:
+ url: https://dev.openchallenges.io/img/unsafe/logo/OpenChallenges-logo.png
+servers:
+ - url: http://localhost/v1
+tags:
+ - name: Dataversion
+ description: Operations about dataversion.
+paths:
+ /dataversion:
+ get:
+ tags:
+ - Dataversion
+ summary: Get dataversion
+ description: Get dataversion
+ operationId: getDataversion
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Dataversion'
+ description: Success
+ '400':
+ $ref: '#/components/responses/BadRequest'
+ '500':
+ $ref: '#/components/responses/InternalServerError'
+components:
+ schemas:
+ Dataversion:
+ type: object
+ description: A dataversion
+ properties:
+ data_file:
+ type: string
+ data_version:
+ type: string
+ team_images_id:
+ type: string
+ required:
+ - data_file
+ - data_version
+ - team_images_id
+ BasicError:
+ type: object
+ description: Problem details (tools.ietf.org/html/rfc7807)
+ properties:
+ title:
+ type: string
+ description: A human readable documentation for the problem type
+ status:
+ type: integer
+ description: The HTTP status code
+ detail:
+ type: string
+ description: A human readable explanation specific to this occurrence of the problem
+ type:
+ type: string
+ description: An absolute URI that identifies the problem type
+ required:
+ - title
+ - status
+ x-java-class-annotations:
+ - '@lombok.AllArgsConstructor'
+ - '@lombok.Builder'
+ responses:
+ BadRequest:
+ description: Invalid request
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/BasicError'
+ InternalServerError:
+ description: The request cannot be fulfilled due to an unexpected server error
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/BasicError'
diff --git a/libs/agora/api-description/openapi-lint-config.yaml b/libs/agora/api-description/openapi-lint-config.yaml
new file mode 100644
index 0000000000..f57d5db0f5
--- /dev/null
+++ b/libs/agora/api-description/openapi-lint-config.yaml
@@ -0,0 +1,4 @@
+errorsOnly: true
+summaryOnly: false
+limits:
+ warnings: 25
diff --git a/libs/agora/api-description/project.json b/libs/agora/api-description/project.json
new file mode 100644
index 0000000000..4a28748f28
--- /dev/null
+++ b/libs/agora/api-description/project.json
@@ -0,0 +1,37 @@
+{
+ "name": "agora-api-description",
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
+ "sourceRoot": "libs/agora/api-description/src",
+ "projectType": "library",
+ "targets": {
+ "build": {
+ "executor": "nx:run-commands",
+ "options": {
+ "command": "redocly bundle --output build/openapi.yaml src/openapi.yaml",
+ "cwd": "{projectRoot}"
+ }
+ },
+ "lint": {
+ "executor": "nx:run-commands",
+ "options": {
+ "command": "lint-openapi --config openapi-lint-config.yaml --ruleset spectral.yaml build/openapi.yaml",
+ "cwd": "{projectRoot}"
+ },
+ "dependsOn": ["build"]
+ },
+ "format": {
+ "executor": "nx:run-commands",
+ "options": {
+ "command": "prettier --write {projectRoot}"
+ }
+ },
+ "format-check": {
+ "executor": "nx:run-commands",
+ "options": {
+ "command": "prettier --check {projectRoot}"
+ }
+ }
+ },
+ "tags": [],
+ "implicitDependencies": []
+}
diff --git a/libs/agora/api-description/spectral.yaml b/libs/agora/api-description/spectral.yaml
new file mode 100644
index 0000000000..1ce3811675
--- /dev/null
+++ b/libs/agora/api-description/spectral.yaml
@@ -0,0 +1,6 @@
+extends: '@ibm-cloud/openapi-ruleset'
+rules:
+ ibm-enum-casing-convention: off
+ ibm-parameter-casing-convention: off
+ ibm-path-segment-casing-convention: off
+ ibm-property-casing-convention: off
diff --git a/libs/agora/api-description/src/components/README.md b/libs/agora/api-description/src/components/README.md
new file mode 100644
index 0000000000..7be6c7d692
--- /dev/null
+++ b/libs/agora/api-description/src/components/README.md
@@ -0,0 +1,13 @@
+# Reusable components
+
+- You can create the following folders here:
+ - `schemas` - reusable [Schema Objects](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject)
+ - `responses` - reusable [Response Objects](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject)
+ - `parameters` - reusable [Parameter Objects](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#parameterObject)
+ - `examples` - reusable [Example Objects](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#exampleObject)
+ - `headers` - reusable [Header Objects](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#headerObject)
+ - `requestBodies` - reusable [Request Body Objects](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#requestBodyObject)
+ - `links` - reusable [Link Objects](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#linkObject)
+ - `callbacks` - reusable [Callback Objects](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#callbackObject)
+ - `securitySchemes` - reusable [Security Scheme Objects](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#securitySchemeObject)
+- Filename of files inside the folders represent component name, e.g. `Customer.yaml`
diff --git a/libs/agora/api-description/src/components/headers/ExpiresAfter.yaml b/libs/agora/api-description/src/components/headers/ExpiresAfter.yaml
new file mode 100644
index 0000000000..0cbe9a5101
--- /dev/null
+++ b/libs/agora/api-description/src/components/headers/ExpiresAfter.yaml
@@ -0,0 +1,4 @@
+description: date in UTC when token expires
+schema:
+ type: string
+ format: date-time
diff --git a/libs/agora/api-description/src/components/responses/BadRequest.yaml b/libs/agora/api-description/src/components/responses/BadRequest.yaml
new file mode 100644
index 0000000000..f1a3d5dd25
--- /dev/null
+++ b/libs/agora/api-description/src/components/responses/BadRequest.yaml
@@ -0,0 +1,5 @@
+description: Invalid request
+content:
+ application/problem+json:
+ schema:
+ $ref: ../schemas/BasicError.yaml
diff --git a/libs/agora/api-description/src/components/responses/Conflict.yaml b/libs/agora/api-description/src/components/responses/Conflict.yaml
new file mode 100644
index 0000000000..d725a306cb
--- /dev/null
+++ b/libs/agora/api-description/src/components/responses/Conflict.yaml
@@ -0,0 +1,5 @@
+description: The request conflicts with current state of the target resource
+content:
+ application/problem+json:
+ schema:
+ $ref: ../schemas/BasicError.yaml
diff --git a/libs/agora/api-description/src/components/responses/InternalServerError.yaml b/libs/agora/api-description/src/components/responses/InternalServerError.yaml
new file mode 100644
index 0000000000..b660ed8ebe
--- /dev/null
+++ b/libs/agora/api-description/src/components/responses/InternalServerError.yaml
@@ -0,0 +1,5 @@
+description: The request cannot be fulfilled due to an unexpected server error
+content:
+ application/problem+json:
+ schema:
+ $ref: ../schemas/BasicError.yaml
diff --git a/libs/agora/api-description/src/components/responses/NotFound.yaml b/libs/agora/api-description/src/components/responses/NotFound.yaml
new file mode 100644
index 0000000000..695a10631a
--- /dev/null
+++ b/libs/agora/api-description/src/components/responses/NotFound.yaml
@@ -0,0 +1,5 @@
+description: The specified resource was not found
+content:
+ application/problem+json:
+ schema:
+ $ref: ../schemas/BasicError.yaml
diff --git a/libs/agora/api-description/src/components/responses/Unauthorized.yaml b/libs/agora/api-description/src/components/responses/Unauthorized.yaml
new file mode 100644
index 0000000000..b7955fdcd4
--- /dev/null
+++ b/libs/agora/api-description/src/components/responses/Unauthorized.yaml
@@ -0,0 +1,5 @@
+description: Unauthorized
+content:
+ application/problem+json:
+ schema:
+ $ref: ../schemas/BasicError.yaml
diff --git a/libs/agora/api-description/src/components/schemas/BasicError.yaml b/libs/agora/api-description/src/components/schemas/BasicError.yaml
new file mode 100644
index 0000000000..08409396e6
--- /dev/null
+++ b/libs/agora/api-description/src/components/schemas/BasicError.yaml
@@ -0,0 +1,22 @@
+type: object
+description: Problem details (tools.ietf.org/html/rfc7807)
+properties:
+ title:
+ type: string
+ description: A human readable documentation for the problem type
+ status:
+ type: integer
+ description: The HTTP status code
+ detail:
+ type: string
+ description: A human readable explanation specific to this occurrence of
+ the problem
+ type:
+ type: string
+ description: An absolute URI that identifies the problem type
+required:
+ - title
+ - status
+x-java-class-annotations:
+ - '@lombok.AllArgsConstructor'
+ - '@lombok.Builder'
diff --git a/libs/agora/api-description/src/components/schemas/Dataversion.yaml b/libs/agora/api-description/src/components/schemas/Dataversion.yaml
new file mode 100644
index 0000000000..314e8b3716
--- /dev/null
+++ b/libs/agora/api-description/src/components/schemas/Dataversion.yaml
@@ -0,0 +1,13 @@
+type: object
+description: A dataversion
+properties:
+ data_file:
+ type: string
+ data_version:
+ type: string
+ team_images_id:
+ type: string
+required:
+ - data_file
+ - data_version
+ - team_images_id
diff --git a/libs/agora/api-description/src/components/schemas/EmptyObject.yaml b/libs/agora/api-description/src/components/schemas/EmptyObject.yaml
new file mode 100644
index 0000000000..abf454afa2
--- /dev/null
+++ b/libs/agora/api-description/src/components/schemas/EmptyObject.yaml
@@ -0,0 +1,2 @@
+type: object
+description: Empty JSON object
diff --git a/libs/agora/api-description/src/openapi.yaml b/libs/agora/api-description/src/openapi.yaml
new file mode 100644
index 0000000000..23005a7ab0
--- /dev/null
+++ b/libs/agora/api-description/src/openapi.yaml
@@ -0,0 +1,20 @@
+openapi: 3.0.3
+info:
+ version: 1.0.0
+ title: Agora REST API
+ license:
+ name: Apache 2.0
+ url: https://github.com/Sage-Bionetworks/sage-monorepo/blob/main/LICENSE.txt
+ contact:
+ name: Support
+ url: https://github.com/Sage-Bionetworks/sage-monorepo
+ x-logo:
+ url: https://dev.openchallenges.io/img/unsafe/logo/OpenChallenges-logo.png
+servers:
+ - url: http://localhost/v1
+tags:
+ - name: Dataversion
+ description: Operations about dataversion.
+paths:
+ /dataversion:
+ $ref: paths/dataversion.yaml
diff --git a/libs/agora/api-description/src/paths/README.md b/libs/agora/api-description/src/paths/README.md
new file mode 100644
index 0000000000..9ec9386823
--- /dev/null
+++ b/libs/agora/api-description/src/paths/README.md
@@ -0,0 +1,107 @@
+# Paths
+
+Organize your path definitions within this folder. You will reference your paths from your main `openapi.yaml` entrypoint file.
+
+It may help you to adopt some conventions:
+
+- path separator token (e.g. `@`) or subfolders
+- path parameter (e.g. `{example}`)
+- file-per-path or file-per-operation
+
+There are different benefits and drawbacks to each decision.
+
+You can adopt any organization you wish. We have some tips for organizing paths based on common practices.
+
+## Each path in a separate file
+
+Use a predefined "path separator" and keep all of your path files in the top level of the `paths` folder.
+
+```
+# todo: insert tree view of paths folder
+```
+
+Redocly recommends using the `@` character for this case.
+
+In addition, Redocly recommends placing path parameters within `{}` curly braces if you adopt this style.
+
+#### Motivations
+
+- Quickly see a list of all paths. Many people think in terms of the "number" of "endpoints" (paths), and not the "number" of "operations" (paths \* http methods).
+
+- Only the "file-per-path" option is semantically correct with the OpenAPI Specification 3.0.2. However, Redocly's openapi-cli will build valid bundles for any of the other options too.
+
+#### Drawbacks
+
+- This may require multiple definitions per http method within a single file.
+- It requires settling on a path separator (that is allowed to be used in filenames) and sticking to that convention.
+
+## Each operation in a separate file
+
+You may also place each operation in a separate file.
+
+In this case, if you want all paths at the top-level, you can concatenate the http method to the path name. Similar to the above option, you can
+
+### Files at top-level of `paths`
+
+You may name your files with some concatenation for the http method. For example, following a convention such as: `-.yaml`.
+
+#### Motivations
+
+- Quickly see all operations without needing to navigate subfolders.
+
+#### Drawbacks
+
+- Adopting an unusual path separator convention, instead of using subfolders.
+
+### Use subfolders to mirror API path structure
+
+Example:
+
+```
+GET /customers
+
+/paths/customers/get.yaml
+```
+
+In this case, the path id defined within subfolders which mirror the API URL structure.
+
+Example with path parameter:
+
+```
+GET /customers/{id}
+
+/paths/customers/{id}/get.yaml
+```
+
+#### Motivations
+
+It matches the URL structure.
+
+It is pretty easy to reference:
+
+```yaml
+paths:
+ '/customers/{id}':
+ get:
+ $ref: ./paths/customers/{id}/get.yaml
+ put:
+ $ref: ./paths/customers/{id}/put.yaml
+```
+
+#### Drawbacks
+
+If you have a lot of nested folders, it may be confusing to reference your schemas.
+
+Example
+
+```
+file: /paths/customers/{id}/timeline/{messageId}/get.yaml
+
+# excerpt of file
+ headers:
+ Rate-Limit-Remaining:
+ $ref: ../../../../../components/headers/Rate-Limit-Remaining.yaml
+
+```
+
+Notice the `../../../../../` in the ref which requires some attention to formulate correctly. While openapi-cli has a linter which suggests possible refs when there is a mistake, this is still a net drawback for APIs with deep paths.
diff --git a/libs/agora/api-description/src/paths/dataversion.yaml b/libs/agora/api-description/src/paths/dataversion.yaml
new file mode 100644
index 0000000000..d41813ac80
--- /dev/null
+++ b/libs/agora/api-description/src/paths/dataversion.yaml
@@ -0,0 +1,19 @@
+get:
+ tags:
+ - Dataversion
+ summary: Get dataversion
+ description: Get dataversion
+ operationId: getDataversion
+ # parameters:
+ # - $ref: ../components/parameters/query/challengeSearchQuery.yaml
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ $ref: ../components/schemas/Dataversion.yaml
+ description: Success
+ '400':
+ $ref: ../components/responses/BadRequest.yaml
+ '500':
+ $ref: ../components/responses/InternalServerError.yaml