Skip to content

REST API Prototype#4259

Open
yannisgu wants to merge 29 commits into
bitfocus:mainfrom
yannisgu:feat/api-prototype
Open

REST API Prototype#4259
yannisgu wants to merge 29 commits into
bitfocus:mainfrom
yannisgu:feat/api-prototype

Conversation

@yannisgu

@yannisgu yannisgu commented Jun 18, 2026

Copy link
Copy Markdown

This is the first prototype for #4101. The idea of this PR is to implement one resource to finalize the open discussion points.

Implementation Scope

  • All endpoints related to connections
  • Authentication is mock implementation with 4 hardcoded tokens for testing (cpn_admin, cpn_write, cpn_read, cpn_secrets)
  • The REST API is currently only enabled if not packed (e.g. in development), there is no UI yet to enable it in the packed version (reason for that is that the PR can be merged, even when the feature is not "done" yet)
  • REST API Docs are created when running yarn run build:rest-api-docs. It is not integrated into the CI build, so that the docs are not published yet, but they can be viewed locally

How to test

Open http://localhost:8000/api/docs/ where API calls can be executed interactively

Kown Open Discussion Points

How should we handle versioning

Option A:
By resource, e.g. connections/v1, buttons/v1. Whenever there is a breaking change in the API of a resource, we would bump the version of this resource only. New resources always start with V1. Note that this is related to the API only. So when we introduce connections/v2, we could keep connections/v1` for compatibility.

A similar approach could be achieved with Accept: application/vnd.companion.v2+json headers, but making it less transparent I think.

Option B
Include the Companion major version in the api path, e.g. api/v4/connections.
Personally I'm not a fan of this option, as every major companion release would break API paths, despite most endpoints probably stay the same

Option C
Global API versioning, so start with api/v1, so that when one day we want to change the structure of the api completly (imaginge in a few years REST is not "best practice" anymore and we want to completly change the API structure).
I guess in this case we would imply that API endpoints with the same path can be breaking across major companion version

Option D

Completely omit versioning for now.

Format of "one" resource

In the discussions there were voices to not have endpoints for an individual resources (e.g. connections/:id), instead having connections endpoint which can be filtered by id, always returning a collection. Personally I don't like that, I feels wrong, not how HTTP works, but eventually it is just a matter of taste. The Companion Core team should reach a decision in this matter and I will implement it accordingly.

Summary by CodeRabbit

  • New Features
    • Added a Companion REST API secured with Bearer tokens and scope-based permissions.
    • Added interactive API docs at /api/docs/ and an OpenAPI spec at /api/openapi.json.
    • Added /api/connections/v1 endpoints to list, create, retrieve, patch, delete, and restart connections (with optional config/secrets access).
  • Behavior Changes
    • REST API is enabled by default only when not running in a packaged build.
    • Connection PATCH can update config by key, and secret access is enforced via scopes.
  • Documentation
    • Added REST API documentation pages covering authentication and scopes (including secret access).
  • Tests
    • Added end-to-end REST API and OpenAPI spec tests, plus UIExpress routing checks.

@CLAassistant

CLAassistant commented Jun 18, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@socket-security

socket-security Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​types/​swagger-ui-express@​4.1.81001007080100
Addedswagger-ui-express@​5.0.110010010080100
Added@​asteasolutions/​zod-to-openapi@​8.5.010010010088100

View full report

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fbac0d77-cdad-49ed-bdb1-bc9cb79bd792

📥 Commits

Reviewing files that changed from the base of the PR and between 122495d and d9ac666.

📒 Files selected for processing (2)
  • companion/lib/Service/RestApi/schemas/connections.ts
  • companion/test/Service/RestApi/ConnectionsRouter.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • companion/lib/Service/RestApi/schemas/connections.ts

📝 Walkthrough

Walkthrough

Introduces a new REST API service for Companion, gated by a rest_api_enabled user-config flag (defaulting to enabled in non-packaged builds). The service mounts Bearer-token authenticated, versioned endpoints under /api/connections/v1 with Zod-validated schemas, scope-based authorization, OpenAPI 3.0 spec generation, Swagger UI, a global error handler, and a Docusaurus documentation generator tool.

Changes

Companion REST API v1

Layer / File(s) Summary
User config flag: rest_api_enabled
shared-lib/lib/Model/UserConfigModel.ts, companion/lib/Data/UserConfig.ts
Adds rest_api_enabled: boolean to UserConfigModel and sets its default to !isPackaged() in DataUserConfig.Defaults.
RestApiError class and global error-handler middleware
companion/lib/Service/RestApi/errors.ts, companion/lib/Service/RestApi/middleware/errorHandler.ts
Introduces RestApiError with HTTP status, string code, and optional details, plus static factories for 400/401/403/404/409/422; adds restApiErrorHandler middleware serializing RestApiError to structured JSON or falling back to a 500 response.
Bearer-token auth contracts, middleware, and in-memory token store
companion/lib/Service/RestApi/RestApiAuth.ts, companion/lib/Service/RestApi/RestApiTokenStore.ts
Defines ApiTokenScope, ApiToken, ApiTokenStore, hasScope scope-implication logic, createAuthMiddleware (header validation + token resolution), and requireScope; provides RestApiTokenStoreMemory backed by a static dev-token map.
OpenAPI registry and spec generator
companion/lib/Service/RestApi/registry.ts, companion/lib/Service/RestApi/openapi.ts
Creates a global OpenAPIRegistry with a bearerAuth security scheme; adds generateOpenApiDocument() that builds an OpenAPI 3.0.3 document with server and security config.
Zod schemas, envelope helpers, and connection response builder
companion/lib/Service/RestApi/schemas/common.ts, companion/lib/Service/RestApi/schemas/connections.ts
Defines reusable pagination/error envelope schemas and runtime helpers; adds ConnectionResponseSchema, ConnectionCreateBodySchema, ConnectionPatchBodySchema, ConfigFieldResponseSchema, and buildConnectionResponse.
Connections router: route handlers, config/secrets validation, and OpenAPI registration
companion/lib/Service/RestApi/routes/ConnectionsRouter.ts, companion/lib/Instance/Controller.ts
Implements createConnectionsRouter with list/create/get/patch/config-fields/delete/restart handlers, scope gating, and validateConfigAndSecrets; registers all OpenAPI path metadata via registerConnectionPaths(); adds patchConfig shallow-merge option to InstanceController.setConnectionLabelAndConfig.
RestApiService, router factory, UIExpress mount point, and ServiceController wiring
companion/lib/Service/RestApi/RestApiService.ts, companion/lib/Service/RestApi/RestApiRouter.ts, companion/lib/UI/Express.ts, companion/lib/Service/Controller.ts, companion/package.json
Adds createRestApiRouter mounting public OpenAPI/Swagger endpoints and auth-gated /connections/v1; introduces RestApiService that conditionally mounts the router via UIExpress.restApiRouter; adds #restApiRouter private field, a terminal 404 handler, and set restApiRouter setter to UIExpress; wires RestApiService as readonly restApi on ServiceController; adds @asteasolutions/zod-to-openapi and swagger-ui-express dependencies.
Tests: ConnectionsRouter, OpenAPI spec, and UIExpress 404 behavior
companion/test/Service/RestApi/ConnectionsRouter.test.ts, companion/test/Service/RestApi/OpenApiSpec.test.ts, companion/test/UI/Express.test.ts
Adds Supertest tests for all connections endpoints (auth, scopes, list/get/create/patch/delete/restart), OpenAPI spec document structure and schema validation, Swagger UI endpoint check, and UIExpress 404 handler tests.
REST API docs generator, static content, and build script
tools/generate_rest_api_docs.mts, tools/rest-api-docs/*, package.json, docs/.gitignore
Adds generate_rest_api_docs.mts that emits Docusaurus-compatible Markdown per operation tag; adds static authentication.md and category intro; registers a build:rest-api-docs script; ignores generated output in docs/.gitignore.

Poem

🔌 A REST API arrives, shiny and new,
With Bearer tokens guarding every route through,
Connections to list, patch, create, and delete,
With Swagger docs making the picture complete —
The endpoints are versioned, the scopes are all right,
Welcome aboard, this PR's a delight! 🎉

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'REST API Prototype' directly and concisely describes the main change—introducing a REST API prototype implementation. It aligns with the PR's primary objective and is clear enough for scanning history.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (4)
companion/test/Service/RestApi/ConnectionsRouter.test.ts (1)

154-182: ⚡ Quick win

Add one positive execute-scope test to lock auth semantics

Nice coverage on denied restart with read-only tokens. I’d also add a positive cpn_execute test for POST /connections/v1/:id/restart (and optionally GET /connections/v1) so regressions in execute => read scope implication are caught early.

Suggested test shape
+ test('execute token can restart connections', async () => {
+   const { app, instanceController } = createService()
+   const executeToken = 'cpn_execute'
+
+   instanceController.getConnectionClientJson.mockReturnValue(createConnectionConfigs())
+   instanceController.restartConnection.mockReturnValue(true)
+
+   const res = await supertest(app)
+     .post('/api/connections/v1/conn-1/restart')
+     .set('Authorization', `Bearer ${executeToken}`)
+     .send()
+
+   expect(res.status).toBe(200)
+ })

Also applies to: 1201-1231

companion/test/Service/RestApi/OpenApiSpec.test.ts (2)

55-59: ⚡ Quick win

Include the config-fields endpoint in expected OpenAPI path coverage

The expectedPaths list currently skips /connections/v1/{connectionId}/config-fields, so OpenAPI regressions for that implemented endpoint could slip through.

Suggested update
 		const expectedPaths = [
 			'/connections/v1',
 			'/connections/v1/{connectionId}',
+			'/connections/v1/{connectionId}/config-fields',
 			'/connections/v1/{connectionId}/restart',
 		]

225-228: ⚡ Quick win

Assert secrets in PATCH request schema fields

Line 225’s expected field list omits secrets, even though PATCH supports secrets updates. Adding it keeps spec tests aligned with runtime behavior and auth-scope coverage.

Suggested update
-			const expectedFields = ['label', 'disabled', 'config', 'updatePolicy', 'versionId']
+			const expectedFields = ['label', 'disabled', 'config', 'secrets', 'updatePolicy', 'versionId']
tools/generate_rest_api_docs.mts (1)

317-317: ⚡ Quick win

Sanitize tag slugs before using them as filenames.

At Line 317, only whitespace is normalized. If a tag contains / or other filename-unsafe chars, docs generation can fail when writing Line 384.

Suggested patch
+function toSafeSlug(tag: string): string {
+	const slug = tag
+		.toLowerCase()
+		.replace(/[^a-z0-9]+/g, '-')
+		.replace(/^-+|-+$/g, '')
+	return slug || 'general'
+}
+
 // Generate one markdown file per tag
 let tagPosition = 1
 for (const [tag, operations] of tagGroups) {
-	const slug = tag.toLowerCase().replace(/\s+/g, '-')
+	const slug = toSafeSlug(tag)
 	const lines: string[] = []

Also applies to: 384-384


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d31e6a31-53ee-4880-a884-4c02620f52fa

📥 Commits

Reviewing files that changed from the base of the PR and between f360335 and 59ea999.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (25)
  • companion/lib/Data/UserConfig.ts
  • companion/lib/Instance/Controller.ts
  • companion/lib/Service/Controller.ts
  • companion/lib/Service/RestApi/RestApiAuth.ts
  • companion/lib/Service/RestApi/RestApiRouter.ts
  • companion/lib/Service/RestApi/RestApiService.ts
  • companion/lib/Service/RestApi/RestApiTokenStore.ts
  • companion/lib/Service/RestApi/errors.ts
  • companion/lib/Service/RestApi/middleware/errorHandler.ts
  • companion/lib/Service/RestApi/openapi.ts
  • companion/lib/Service/RestApi/registry.ts
  • companion/lib/Service/RestApi/routes/ConnectionsRouter.ts
  • companion/lib/Service/RestApi/schemas/common.ts
  • companion/lib/Service/RestApi/schemas/connections.ts
  • companion/lib/UI/Express.ts
  • companion/package.json
  • companion/test/Service/RestApi/ConnectionsRouter.test.ts
  • companion/test/Service/RestApi/OpenApiSpec.test.ts
  • companion/test/UI/Express.test.ts
  • docs/.gitignore
  • package.json
  • shared-lib/lib/Model/UserConfigModel.ts
  • tools/generate_rest_api_docs.mts
  • tools/rest-api-docs/_category-intro.md
  • tools/rest-api-docs/authentication.md

Comment thread companion/lib/Service/RestApi/middleware/errorHandler.ts
Comment thread companion/lib/Service/RestApi/RestApiAuth.ts
Comment thread companion/lib/Service/RestApi/RestApiService.ts
Comment thread companion/lib/Service/RestApi/RestApiTokenStore.ts
Comment thread companion/lib/Service/RestApi/routes/ConnectionsRouter.ts
Comment thread companion/lib/Service/RestApi/routes/ConnectionsRouter.ts Outdated
Comment thread companion/lib/Service/RestApi/schemas/connections.ts Outdated
Comment thread tools/rest-api-docs/_category-intro.md
Comment thread tools/rest-api-docs/authentication.md Outdated
Comment thread tools/rest-api-docs/authentication.md
yannisgu and others added 3 commits June 18, 2026 10:15
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants