| title | Environment-Scoped Routing |
|---|---|
| description | Route REST, metadata, automation, AI, and package calls through /api/v1/environments/:environmentId/.... |
Environment-scoped routing makes the target runtime explicit in the URL:
/api/v1/environments/:environmentId/data/:object
/api/v1/environments/:environmentId/meta
/api/v1/environments/:environmentId/automation/...
/api/v1/environments/:environmentId/ai/...
The scoped URL wins over hostname, header, session, and default-environment resolution. Use it when a caller must address a specific environment without depending on request context.
Enable scoped route registration in objectstack.config.ts:
{/* os:check */}
import { defineStack } from '@objectstack/spec';
export default defineStack({
// ...your manifest, objects, apis, etc.
api: {
enableProjectScoping: true,
projectResolution: 'auto',
},
});The option names are historical for compatibility with existing config files;
the route, header, env var, and request context all use environment.
| Strategy | Behavior | When to use |
|---|---|---|
auto |
Registers both unscoped /api/v1/... and scoped /api/v1/environments/:environmentId/... routes. |
Default migration mode. |
optional |
Same route surface as auto; resolution may come from URL, hostname, header, session, or default. |
Multi-environment hosts that still accept unscoped callers. |
required |
Registers only environment-scoped routes for data/meta/AI/automation/package handlers. | Hardened clients that always pass an environment id. |
The client keeps the legacy factory name project(id), but the value is an
environment id and the generated URLs are environment-scoped:
import { ObjectStackClient } from '@objectstack/client';
const client = new ObjectStackClient({
baseUrl: 'https://api.example.com',
});
const env = client.project('env_prod');
await env.data.find('customer', { top: 20 });
await env.meta.getItems('object');
await env.packages.list();The top-level client can also set a default environment header:
const client = new ObjectStackClient({
baseUrl: 'https://api.example.com',
environmentId: 'env_prod',
});That sends X-Environment-Id: env_prod on unscoped requests.
The open-source HttpDispatcher only parses the scoped URL for an
environment-id hint (extractEnvironmentIdFromPath) and forwards it via
prepareResolverHints. Actual resolution is owned by the host's
KernelResolver (resolveKernel, per ADR-0006 Phase 5), which resolves the
environment in this order:
:environmentIdfrom/api/v1/environments/:environmentId/...- Hostname through the configured environment registry
X-Environment-Idsession.activeEnvironmentId- A configured default environment
- A single available environment, when the registry can prove the choice is unambiguous
Control-plane endpoints remain unscoped. Requests such as
/api/v1/cloud/environments/:id manage environments rather than running inside
one, so they are intentionally excluded from data-plane resolution.
- Replace
/api/v1/projects/:projectId/...with/api/v1/environments/:environmentId/.... - Replace
X-Project-IdwithX-Environment-Id. - Replace
OS_PROJECT_IDwithOS_ENVIRONMENT_ID. - Store runtime rows under
environment_id. - Keep the SDK
client.project(id)call only as a compatibility method name; pass an environment id to it.