REST APIs
A REST API (RAPI) serves your dcupl application over HTTP: an instance on an app runner loads the application once and answers queries against it, with an OpenAPI document and an API key.
Overview
flowchart LR
subgraph Console
T[API Template]
D[Deploy]
end
subgraph Runner
I[REST API instance]
E[Endpoints]
end
subgraph Client["Client Apps"]
W[Web App]
M[Mobile App]
B[Backend]
end
T --> D
D --> I
I --> E
E --> W
E --> M
E --> B
A REST API instance provides:
- Query endpoints for every model of the loaded application —
query,facet,aggregate,pivot,suggest,metadata(see Query endpoints) - Scripted endpoints (part of a paid plan) — your own routes answered by project scripts, see Scripted endpoints
- MCP endpoint (part of a paid plan) — let AI agents such as Claude Code query an instance with its API key, see MCP endpoint
- OpenAPI/Swagger documentation for API exploration
- API key authentication — one key per template, sent as header or query parameter
- v-Guard caching for version-based cache invalidation
- A status route — what the instance loaded, and why a start failed (see Status Endpoint)
Setting up a REST API
The REST APIs entry appears for project owners and editors. REST APIs are on for new projects; if the entry is missing, ask your dcupl contact to turn them on for your project. The number of deployed instances per project is capped; the Templates tab shows it as Deployed: X/max.
Step 1: create an API template
- Navigate to REST APIs in your project
- On the Templates tab, click + (or Template while the project has none) and enter a name — the Console creates
api/<key>.api.jsonand opens it in the form editor - Fill in the configuration:
{
"key": "customer-api",
"name": "Customer API",
"description": "REST API for customer data",
"vGuardEnabled": false,
"auth": [
{
"type": "api-key",
"value": "your-secure-api-key-here"
}
],
"dcuplConfig": {
"projectId": "your-project-id",
"apiKey": "your-sdk-readonly-key",
"processOptions": {
"applicationKey": "production",
"environmentKeys": ["prod"]
}
}
}Step 2: deploy to runner
- Open the template card's ⋮ menu and choose Deploy (a template that is not deployed yet also shows a Not deployed tag that opens the same form), or click the deploy button in the toolbar
- In Deploy an Instance, pick the Runner and the Template
- Click Deploy
- Open the runner's tab and wait for the status to show
ready
An instance loads its data once, when it starts. To pick up changed models, data or template settings, select it on the runner's tab and click Update (a redeploy); the instance answers from the previous data until the reload is done (status updating).
Step 3: access your API
Your API is now available at:
{runner-url}/instance/{instance-key}/{runner-url} is the URL shown on the runner's tab. {instance-key} is the key in the Instance column of that tab — deploying prefixes the template key with your project ID (<projectId>-customer-api), so it is neither the template key alone nor the API key. A runner set up without the prefix uses the bare template key; the Instance column always shows the key to call. The examples below write it as your-project-id-customer-api.
Authentication
API key authentication
An instance supports API key authentication via query parameter or header:
curl 'https://runner.example.com/instance/your-project-id-customer-api/status?api-key=your-api-key'curl 'https://runner.example.com/instance/your-project-id-customer-api/status' \
-H 'api-key: your-api-key'The query parameter may also be spelled apiKey or Api-Key; as a header only api-key works (header names are case-insensitive, apiKey is not accepted). A missing or wrong key — and an unknown instance key — is answered with 403 Forbidden resource.
Best Practice: Use header-based authentication in production as it's more secure and doesn't expose keys in URLs or logs.
One API key per instance
The runner checks the first api-key entry of auth and ignores any further entries, so give each consumer the same key (the Console form has a single Api Key field). A template without an api-key entry, or with an empty value, deploys an open instance that answers anyone who knows its URL.
{
"auth": [{ "type": "api-key", "value": "your-secure-api-key-here" }]
}API endpoints
Status endpoint
Check if the instance is ready:
GET /instance/your-project-id-customer-api/status?api-key=your-key{
"key": "your-project-id-customer-api",
"uid": "…",
"status": "ready",
"v": "3f2a…",
"createdAt": "2026-10-12T09:00:00.000Z",
"changedAt": "2026-10-12T09:00:00.000Z",
"instanceChangedAt": "2026-10-12T09:00:04.000Z",
"report": {
"resources": { "scheduled": 4, "skipped": [], "unresolvedVariables": [] },
"quality": { "errorCount": 0, "errors": [] },
"models": 3,
"items": 1250,
"durationMs": 3120
}
}v is the version hash that v-Guard checks. report describes what the last start loaded (resources skipped with the reason, the first 20 quality errors). While the status is error, an error object (message, name, phase: fetch-config, process, init or worker, at) says why. templateVersion and runnerInstanceId are optional.
Status values:
| Status | Description |
|---|---|
waiting |
Registered, not started yet |
processing |
Loading the loader configuration and data |
ready |
Accepting requests |
updating |
Reloading after a configuration change; the previous data answers meanwhile |
error |
Start failed (or the instance crashed later) — see error |
stopped |
Stopped on the runner |
The data routes answer 400 instance not ready while the status is waiting, processing, error or stopped.
Overview endpoint
List the models of the loaded application with their item counts:
GET /instance/your-project-id-customer-api/overview?api-key=your-key{
"models": [
{ "key": "customer", "count": 1200 },
{ "key": "order", "count": 48 }
]
}Details and invocations
GET /instance/your-project-id-customer-api/details?api-key=your-key
GET /instance/your-project-id-customer-api/invocations?action=query&api-key=your-keydetails is the status response plus runtime (the instance's heap figures, used_heap_size and max_heap_size, in megabytes; both 0 when the instance cannot answer, e.g. while it is not ready) and invocations. invocations lists the instance's call counters, each with completed and failed: one row for the instance and one per action — the query routes by name (query, facet, …), endpoint and endpoint:<path> for scripted endpoints, mcp:<tool> (mcp:query, mcp:endpoint:<path>) for MCP calls. action selects one of them. The counters live in the runner's memory and start from zero when the runner restarts. Unlike status, overview and details, invocations needs the current v while v-Guard is on.
Swagger documentation
Open the interactive Swagger UI in a browser:
GET /instance/your-project-id-customer-api/swaggerThe OpenAPI 3.0 document itself (JSON) is served at GET /instance/your-project-id-customer-api/swagger-json. Neither needs the API key — they describe the routes and contain no secrets; enter the key in the Swagger UI to try requests.
Query endpoints
The data routes run the dcupl SDK's query and function API against the instance's loaded data. Each exists as GET (options as a JSON string in the data query parameter) and POST (the same options as a JSON body, answered with 201):
| Route | Options | Runs |
|---|---|---|
query |
{ modelKey, queries, start?, count?, … } |
dcupl.query.execute() |
facet |
{ modelKey, options, query? } |
dcupl.fn.facets() |
aggregate |
{ modelKey, options, query? } |
dcupl.fn.aggregate() |
pivot |
{ modelKey, options, query? } |
dcupl.fn.pivot() |
suggest |
{ modelKey, options, query? } |
dcupl.fn.suggest() |
metadata |
{ modelKey, query? } |
dcupl.fn.metadata() |
curl -X POST 'https://runner.example.com/instance/your-project-id-customer-api/query' \
-H 'api-key: your-api-key' \
-H 'content-type: application/json' \
-d '{ "modelKey": "customer", "queries": [{ "attribute": "status", "operator": "eq", "value": "active" }], "count": 10 }'The response is the SDK's result as JSON (for query, the array of items). The options are those of Functions and the query operators. An unparsable data string, invalid options or an instance that is not ready yield 400.
Query examples
There are no routes per model: name the model in modelKey and describe what you want with dcupl query operators. queries combine with 'and' unless you set groupType: 'or'.
curl -X POST 'https://runner.example.com/instance/your-project-id-customer-api/query' \
-H 'api-key: your-api-key' -H 'content-type: application/json' \
-d '{ "modelKey": "customer", "queries": [{ "attribute": "key", "operator": "eq", "value": "c1" }] }'curl -X POST 'https://runner.example.com/instance/your-project-id-customer-api/query' \
-H 'api-key: your-api-key' -H 'content-type: application/json' \
-d '{
"modelKey": "customer",
"queries": [
{ "attribute": "status", "operator": "in", "value": ["active", "trial"] },
{ "attribute": "age", "operator": "gt", "value": 25 }
],
"sort": { "attributes": ["name"], "order": ["ASC"] },
"start": 20,
"count": 10
}'sort.order takes 'ASC' or 'DESC' (uppercase), one per attribute. start (0-based) and count page the result.
Scripted endpoints
Besides the query routes, a template can declare its own routes under /instance/{key}/endpoints/…, each answered by a script from your project:
{
"endpoints": [{ "path": "orders/:id", "method": "GET", "scriptKey": "order-detail" }]
}GET /instance/your-project-id-customer-api/endpoints/orders/42?api-key=your-keyThe script receives the request (path parameters, query, JSON body) and a read-only view of the instance's data; its return value is the JSON response. Scripted endpoints are part of a paid plan that dcupl turns on for your project; ask your dcupl contact. See Scripted endpoints for the template fields, the route, status codes and the Console tooling.
MCP endpoint
With "mcp": { "enabled": true } in the template, the instance also serves POST /instance/{key}/mcp, a Model Context Protocol endpoint. Any MCP-capable agent — Claude Code, Cursor, VS Code — connects to it with the URL and the instance's API key and gets read-only tools over the deployed data plus one tool per scripted endpoint. MCP is part of a paid plan that dcupl turns on for your project; ask your dcupl contact. See MCP endpoint for turning it on, connecting an agent, the tools and the error answers.
v-Guard caching
Enable version-based cache invalidation for improved performance:
{
"vGuardEnabled": true
}How v-Guard works
Data responses (the query endpoints and scripted endpoints) are cacheable for an hour: cache-control: private, max-age=3600 for an instance with an API key, public, max-age=3600 for an open one, no-store on errors. The instance's v — a hash of its deployed configuration, read from /status — changes with every redeploy (Update), not when only the data behind it changes. With vGuardEnabled: true, every data request must carry the current v as query parameter or header, so a new configuration means new URLs and old cache entries are never reused. A missing or outdated v is answered with 400 Invalid v parameter.
sequenceDiagram
participant Client
participant RAPI as REST API instance
Client->>RAPI: GET /status
RAPI-->>Client: { status: "ready", v: "3f2a…" }
Client->>RAPI: POST /query?v=3f2a…
alt v matches
RAPI-->>Client: 201 result (cacheable)
else v outdated or missing
RAPI-->>Client: 400 Invalid v parameter
Client->>RAPI: GET /status (read the new v, retry)
end
Client integration
class RAPIClient {
private v: string | null = null;
constructor(
private baseUrl: string, // {runner-url}/instance/{instance-key}
private apiKey: string,
) {}
private async currentV(): Promise<string> {
if (!this.v) {
const response = await fetch(`${this.baseUrl}/status`, { headers: { 'api-key': this.apiKey } });
this.v = (await response.json()).v as string;
}
return this.v;
}
async query(options: object, retry = true): Promise<unknown> {
const v = await this.currentV();
const response = await fetch(`${this.baseUrl}/query?v=${encodeURIComponent(v)}`, {
method: 'POST',
headers: { 'api-key': this.apiKey, 'content-type': 'application/json' },
body: JSON.stringify(options),
});
// 400: the configuration changed (or the instance is not ready) — re-read v once
if (response.status === 400 && retry) {
this.v = null;
return this.query(options, false);
}
return response.json();
}
}When to enable v-Guard: when responses pass through a browser or CDN cache and a client must never mix answers from two deployments. The cost: every client has to read v from /status first and read it again after a 400.
When to leave it off: for simple server-to-server callers. The one-hour cache-control applies either way.
Configuration reference
Template structure
interface RAPITemplate {
key: string; // Unique identifier (alphanumeric, -, _)
name?: string; // Display name
description?: string; // Optional description
version?: string; // Optional version string
vGuardEnabled: boolean; // Enable version-based caching
auth?: AuthConfig[]; // Only the first api-key entry is checked; none = open instance
dcuplConfig: DcuplConfig; // dcupl integration config
endpoints?: TemplateEndpoint[]; // Scripted endpoints (need them on your plan, see /console/rapi/scripted-endpoints)
mcp?: { enabled: boolean }; // Serve POST /instance/{instance-key}/mcp (needs MCP on your plan, see /console/rapi/mcp)
}
interface AuthConfig {
type: 'api-key';
value: string;
}
interface TemplateEndpoint {
path: string; // e.g. 'orders/:id'
method: 'GET' | 'POST';
scriptKey: string;
description?: string;
}
interface DcuplConfig {
projectId: string;
apiKey: string; // SDK readonly key
processOptions: {
applicationKey?: string; // Select application preset
environmentKeys?: string[]; // Select environments
resourceTags?: (string | string[])[]; // Filter by resource tags
variables?: { key: string; value: string | undefined; description?: string }[];
options?: AppLoaderConfiguration.Options; // Replace the loader file's top-level options, see /sdk/api/loader#processoptions
};
loaderFetchOptions?: {
baseUrl?: string; // Custom CDN URL
loaderFileName?: string; // Custom loader filename
headers?: { key: string; value: string }[]; // Sent when fetching the loader file from a custom baseUrl, e.g. authorization
};
}Troubleshooting
Instance status: error
Causes:
- Invalid SDK API key
- Project not accessible
- Data loading failed
Solutions:
- Verify SDK API key in template
- Check project permissions
- Read
error.phase/error.messageandreportin the status response
Endpoints not responding
Call the status endpoint first: its status says whether the instance is loaded, and a 403 means the API key or the instance key is wrong (the instance key carries your project ID as a prefix).
High memory usage
Causes:
- Large datasets
- Too many models loaded
Solutions:
- Load fewer resources: select them with
processOptions.resourceTagsor an application whose tags cover only what the API needs - Contact dcupl if the instance still needs more memory than its runner allows