Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
706 changes: 656 additions & 50 deletions docs-mintlify/api-reference/api.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs-mintlify/api-reference/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ Resources by entity:
| [Embed](/api-reference/embed/get-an-embeddable-dashboard) | `/api/v1/embed` | v1 |
| [Embed Tenants](/api-reference/embed-tenants/list-embed-tenants) | `/api/v1/embed-tenants` | v1 |
| [Dashboard Embed Access](/api-reference/dashboard-embed-access/list-a-dashboards-embed-access) | `/api/v1/deployments/{deploymentId}/workbooks/{workbookId}/embed-access` | v1 |
| [OpenAPI Spec](/api-reference/openapi-spec/get-the-openapi-specification) | `/api/v1/spec` | v1 |
{/* AUTOGEN:platform-endpoints END */}
| [Users (SCIM)](/api-reference/scim-users/list-users) | `/scim/v2/Users` | SCIM 2.0 |
| [Groups (SCIM)](/api-reference/scim-groups/list-groups) | `/scim/v2/Groups` | SCIM 2.0 |
Expand Down
22 changes: 19 additions & 3 deletions docs-mintlify/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,9 @@
"POST /api/v1/deployments/{deploymentId}/creation-step/reset",
"GET /api/v1/deployments/{deploymentId}/logs",
"GET /api/v1/deployments/{deploymentId}/pods",
"POST /api/v1/deployments/{deploymentId}/token"
"GET /api/v1/deployments/{deploymentId}/settings",
"POST /api/v1/deployments/{deploymentId}/token",
"GET /api/v1/deployments/{deploymentId}/versions"
]
},
{
Expand Down Expand Up @@ -803,6 +805,7 @@
"pages": [
"GET /build/api/v1/deployments/{deploymentId}/branches",
"POST /build/api/v1/deployments/{deploymentId}/branches",
"PUT /build/api/v1/deployments/{deploymentId}/branches/staging-environment",
"POST /build/api/v1/deployments/{deploymentId}/commit",
"GET /build/api/v1/deployments/{deploymentId}/data-model/files",
"PUT /build/api/v1/deployments/{deploymentId}/data-model/files",
Expand Down Expand Up @@ -846,7 +849,10 @@
"group": "dbt Sync",
"openapi": "/api-reference/api.yaml",
"pages": [
"POST /api/v1/deployments/{deploymentId}/dbt-sync"
"POST /api/v1/deployments/{deploymentId}/dbt-sync",
"GET /api/v1/deployments/{deploymentId}/dbt-sync/{syncJobId}",
"DELETE /api/v1/deployments/{deploymentId}/dbt-sync/{syncJobId}",
"GET /api/v1/deployments/{deploymentId}/dbt-sync/{syncJobId}/result"
]
},
{
Expand Down Expand Up @@ -885,7 +891,6 @@
"PUT /api/v1/deployments/{deploymentId}/workbooks/{workbookId}",
"DELETE /api/v1/deployments/{deploymentId}/workbooks/{workbookId}",
"PUT /api/v1/deployments/{deploymentId}/workbooks/{workbookId}/dashboard",
"POST /api/v1/deployments/{deploymentId}/workbooks/{workbookId}/dashboard/ai-widget-thread",
"POST /api/v1/deployments/{deploymentId}/workbooks/{workbookId}/duplicate",
"POST /api/v1/deployments/{deploymentId}/workbooks/{workbookId}/publish"
]
Expand Down Expand Up @@ -1025,6 +1030,13 @@
"PUT /api/v1/deployments/{deploymentId}/workbooks/{workbookId}/embed-access"
]
},
{
"group": "OpenAPI Spec",
"openapi": "/api-reference/api.yaml",
"pages": [
"GET /api/v1/spec"
]
},
{
"group": "Users",
"openapi": "/api-reference/scim.yaml",
Expand Down Expand Up @@ -1238,6 +1250,10 @@
{
"source": "/docs/integrations/power-bi/ntlm",
"destination": "/reference/core-data-apis/dax-api/ntlm"
},
{
"source": "/api-reference/workbooks/update-published-dashboard-ai-widget-thread",
"destination": "/api-reference/workbooks/update-workbook-dashboard"
}
]
}
79 changes: 78 additions & 1 deletion docs-mintlify/reference/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ Run `cube <command> --help` for the full options of any command.
| Command | Description |
| --- | --- |
| `login`, `logout`, `whoami`, `context` | Authentication and saved contexts |
| `deployments` | List, get, create, update, delete deployments; `token`, `build-status`, `advance-step`, `reset-step` |
| `deployments` | List, get, create, update, delete deployments; `settings`, `versions`, `token`, `build-status`, `advance-step`, `reset-step` |
| `deploy` | Upload a local project directory and build it |
| `logs` | Tail deployment pod logs (`--pod`, `-c/--container`, `--source production\|dev`) |
| `regions` | List available deployment regions |
Expand All @@ -159,13 +159,89 @@ Run `cube <command> --help` for the full options of any command.
| `tenant`, `notifications`, `integrations`, `oidc`, `api-keys` | Account administration |
| `embed` | Embed sessions, tokens, embed tenants; `enable-dashboard`/`disable-dashboard` toggle signed embedding for a dashboard |
| `agents`, `app`, `meta`, `scim` | Agents, app config, model metadata, SCIM v2 |
| `spec` | Show the API's OpenAPI specification — see [Discovering the API](#discovering-the-api) |
| `api` | Raw authenticated API request (escape hatch): `cube api GET /api/v1/... -q key=value -d '{...}'` |
| `update` | Update the CLI to the latest release |
| `completion` | Generate shell completions |

List commands print tables by default; pass `--json` anywhere for raw JSON
output, suitable for piping to `jq`.

## Changing the Cube version

`cube deployments versions` lists the Cube versions a deployment can switch to
— the head of each [update channel][ref-update-channels], plus the older
versions your account has run before:

```bash
cube deployments versions DEPLOYMENT_ID
```

```
VERSION CHANNEL LATEST CURRENT PASS AS
1.7.20 latest true true cubejs/cube:v1.7.20
1.6.69 latest false false cubejs/cube:v1.6.69
```

Apply one with `update`. Any of `1.7.20`, `v1.7.20` or `cubejs/cube:v1.7.20` is
accepted; a version that is not on the list is rejected. The container image is
resolved from the version, so there is nothing else to set:

```bash
cube deployments update DEPLOYMENT_ID --release-channel-version 1.7.20
cube deployments update DEPLOYMENT_ID --release-channel release # move to a channel's latest
```

`cube deployments settings DEPLOYMENT_ID` reads back every setting, including
the version and channel currently in effect.

## Discovering the API

`cube spec` prints the OpenAPI specification of the API you are logged into, so
neither you nor an AI agent has to guess an endpoint's parameters. It reads
`/api/v1/spec` from the deployment itself, which means the contract you get is
the one that build actually serves.

With no arguments it lists every operation:

```bash
cube spec
```

```
METHOD PATH SUMMARY
GET /api/v1/deployments Get deployments
PUT /api/v1/deployments/{deploymentId} Update a deployment
...
```

Pass a pattern to narrow it down. The match is case-insensitive and covers the
method, path, summary, and operation id:

```bash
cube spec settings
```

Add `--json` to get OpenAPI instead of a table. Unfiltered, that is the entire
document — pipe it into a code generator or a validator. Filtered, it is a
smaller but still valid document containing just the matching operations plus
every schema they reference, transitively:

```bash
cube spec updateDeployment --json
```

That last form is the one to reach for when you want an endpoint's full
parameter list: the request body's schema is included rather than left as a
`$ref` pointing into a document you would then have to fetch in full.

<Tip>

Point an agent at `cube spec <topic> --json` and it can construct a correct
request without any hardcoded knowledge of the API.

</Tip>

## Data model Git workflow

Edit the data model through branches without touching production:
Expand Down Expand Up @@ -224,3 +300,4 @@ or explicitly with `CUBE_NO_TELEMETRY=1` (or the legacy `CUBEJS_TELEMETRY=false`
[ref-api-keys]: /admin/account-billing/api-keys
[ref-rest-api]: /reference/core-data-apis/rest-api/index
[ref-staging-env]: /admin/deployment/environments#staging-environments
[ref-update-channels]: /admin/deployment#update-channels
56 changes: 55 additions & 1 deletion docs-mintlify/reference/control-plane-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,59 @@ Example response:
The same operation is available in the [CLI][ref-cli] as
`cube data-model enable-branch` / `cube data-model disable-branch`.

### `/api/v1/deployments/{deployment_id}/versions`

Send a `GET` request to list the Cube versions a deployment can be switched
to — the same set the Cube Cloud UI's version picker offers: the head of each
[update channel][ref-update-channels], plus the older versions your account has
run before.

Example request:

```bash
curl \
-H "Authorization: Bearer YOUR_API_KEY" \
"https://YOUR_CUBE_CLOUD_HOST/api/v1/deployments/123/versions"
```

Example response:

```json
{
"data": [
{
"releaseChannelVersion": "cubejs/cube:v1.7.20",
"version": "1.7.20",
"releaseChannel": "latest",
"isLatestInChannel": true,
"isCurrent": true
},
{
"releaseChannelVersion": "cubejs/cube:v1.6.69",
"version": "1.6.69",
"releaseChannel": "latest",
"isLatestInChannel": false,
"isCurrent": false
}
]
}
```

To change the version, send a listed value as `releaseChannelVersion` to
`PUT /api/v1/deployments/{deployment_id}`. Any of `1.7.20`, `v1.7.20` or
`cubejs/cube:v1.7.20` is accepted; a version that is not on the list is
rejected with a `400`. The container image is resolved from the version
server-side and cannot be set directly.

<Note>
Leaving `releaseChannelVersion` out and sending only `releaseChannel` moves the
deployment to that channel's latest version.
</Note>

The same operation is available in the [CLI][ref-cli] as
`cube deployments versions`, with `cube deployments update ID
--release-channel-version 1.7.20` to apply one.

### `/api/v1/audit-logs/export`

Send a `GET` request to export [audit log][ref-audit-log] events as a CSV
Expand Down Expand Up @@ -327,4 +380,5 @@ curl \
[ref-cli]: /reference/cli
[ref-api-keys]: /admin/account-billing/api-keys
[ref-security-context]: /docs/data-modeling/access-control/context
[ref-audit-log]: /admin/monitoring/audit-log
[ref-audit-log]: /admin/monitoring/audit-log
[ref-update-channels]: /admin/deployment#update-channels
3 changes: 3 additions & 0 deletions docs-mintlify/scripts/extract-api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ const TAG_MAP = {
'Deployments Build Public': 'Deployment Creation',
'Uploads Public': 'Data Model Uploads',
'Git Hub Deployment Public': 'GitHub Connection',
// Auto-cleaning title-cases the controller name into "Open Api Spec".
'Open Api Spec Public': 'OpenAPI Spec',
};
// Preferred nav order. Tags not listed here are appended alphabetically, so the
// docs stay complete even when the upstream spec adds new areas.
Expand All @@ -186,6 +188,7 @@ const TAG_ORDER = [
'User Attributes', 'User Attribute Values', 'Resource Policies', 'Tenant Settings',
'OAuth Integrations', 'User OAuth Tokens', 'OIDC Token Configs',
'App Theme', 'AI Engineer', 'Embed', 'Embed Tenants', 'Dashboard Embed Access',
'OpenAPI Spec',
];

// Mintlify renders the OpenAPI operation `description` as a plain-text node — it
Expand Down
15 changes: 14 additions & 1 deletion packages/cubejs-api-gateway/src/gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,18 @@ function systemAsyncHandler(handler: (req: Request & { context: ExtendedRequestC
};
}

const DEV_TOKEN_SCOPE = 'dev-token';

function hasDevTokenScope(securityContext: unknown): boolean {
if (typeof securityContext !== 'object' || securityContext === null) {
return false;
}

const { scope } = <Record<string, any>>securityContext;

return Array.isArray(scope) && scope.includes(DEV_TOKEN_SCOPE);
}

// Prepared CheckAuthFn, default or from config: always async
type PreparedCheckAuthFn = (ctx: any, authorization?: string) => Promise<{
securityContext: any;
Expand Down Expand Up @@ -2615,7 +2627,8 @@ class ApiGateway {
if (auth) {
try {
req.securityContext = await checkAuthFn(auth);
req.signedWithPlaygroundAuthSecret = Boolean(internalOptions?.isPlaygroundCheckAuth);
req.signedWithPlaygroundAuthSecret =
Boolean(internalOptions?.isPlaygroundCheckAuth) && hasDevTokenScope(req.securityContext);
} catch (e: any) {
if (this.enforceSecurityChecks) {
throw new CubejsHandlerError(403, 'Forbidden', 'Invalid token', e);
Expand Down
63 changes: 63 additions & 0 deletions packages/cubejs-api-gateway/test/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,69 @@ describe('test authorization', () => {
expectSecurityContext(handlerMock.mock.calls[0][0].context.authInfo);
});

describe('signedWithPlaygroundAuthSecret requires the dev-token scope', () => {
const playgroundAuthSecret = 'playgroundSecret';
const loggerMock = jest.fn(() => {
//
});

// The playground secret signs every token a Cube Cloud deployment mints,
// including ones handed to end users and external BI tools, so the
// signature alone must not unlock the developer affordances gated on this
// flag (hidden meta members, generated SQL, pre-aggregation debug info).
const flagFor = async (payload: Record<string, any>) => {
const seen: boolean[] = [];
const handlerMock = jest.fn((req, res) => {
seen.push(req.context.signedWithPlaygroundAuthSecret);
res.status(200).end();
});

const { app } = createApiGateway(handlerMock, loggerMock, { playgroundAuthSecret });

await request(app)
.get('/test-auth-fake')
.set('Authorization', `Authorization: ${generateAuthToken(payload, {}, playgroundAuthSecret)}`)
.expect(200);

return seen[0];
};

test('is false for a playground-signed token with no scope at all', async () => {
expect(await flagFor({ uid: 5 })).toBe(false);
});

test('is false for a playground-signed token scoped to something else', async () => {
expect(await flagFor({ uid: 5, scope: ['sql-runner', 'agents-config'] })).toBe(false);
});

test('is true for a playground-signed token carrying the dev-token scope', async () => {
expect(await flagFor({ uid: 5, scope: ['dev-token'] })).toBe(true);
// Alongside the service scopes it is minted with in practice.
expect(await flagFor({ uid: 5, scope: ['sql-runner', 'dev-token'] })).toBe(true);
});

test('is false when scope is not an array of scope names', async () => {
expect(await flagFor({ uid: 5, scope: 'dev-token' })).toBe(false);
expect(await flagFor({ uid: 5, scope: { 'dev-token': true } })).toBe(false);
});

test('is false for a token signed with the main api secret, scope or not', async () => {
const handlerMock = jest.fn((req, res) => {
expect(req.context.signedWithPlaygroundAuthSecret).toBe(false);
res.status(200).end();
});

const { app } = createApiGateway(handlerMock, loggerMock, { playgroundAuthSecret });

await request(app)
.get('/test-auth-fake')
.set('Authorization', `Authorization: ${generateAuthToken({ uid: 5, scope: ['dev-token'] }, {})}`)
.expect(200);

expect(handlerMock.mock.calls.length).toEqual(1);
});
});

test('default authorization with JWT token and securityContext in u', async () => {
const loggerMock = jest.fn(() => {
//
Expand Down
3 changes: 2 additions & 1 deletion rust/cube-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Every endpoint of the Console Server public API is covered:

| Group | Endpoints |
|---|---|
| `deployments` | list, get, create (`--bootstrap` scaffolds + builds a serving deployment), update, delete, token, advance-step, reset-step |
| `deployments` | list, get, create (`--bootstrap` scaffolds + builds a serving deployment), update (`--release-channel`, `--release-channel-version`), settings, versions, delete, token, advance-step, reset-step |
| `regions` | list available deployment regions |
| `logs` | tail deployment pod logs (`--pod`, `-c/--container`; defaults to the Cube API container) |
| `github` (`gh`) | status, installations, repos, branches, connect (import a repo into a deployment + first build) |
Expand All @@ -152,6 +152,7 @@ Every endpoint of the Console Server public API is covered:
| `app` | config, theme |
| `meta` | POST /api/v1/meta/ |
| `scim` | Users/Groups CRUD + patch, resource-types, schemas, service-provider-config |
| `spec` | the API's own OpenAPI document from `/api/v1/spec`: bare lists every operation, `<pattern>` filters on method/path/summary/operationId, `--json` prints OpenAPI (filtered = matching operations + the transitive schema closure) |
| `api` | raw escape hatch: `cube api GET /api/v1/... -q key=value -d '{...}'` |

Conventions:
Expand Down
Loading
Loading