Programmatic Instance Management - EyevinnOSC/community GitHub Wiki
This page documents the correct HTTP contract for managing OSC service instances from scripts, pipelines, and applications without using the web console or MCP tools.
POST https://api.osaas.io/service/<serviceId> is not a supported write endpoint and must not be used in new integrations. The api.osaas.io host became a cache-only CloudFront distribution and rejects all write methods (POST, DELETE, PATCH) with a CloudFront 403 error. Existing pipelines that target this host will break.
Use the per-service apiUrl contract described below instead.
The authoritative implementation is @osaas/client-core. The following describes the HTTP calls it makes, so you can replicate the same behaviour in any language.
Generate a long-lived PAT from the OSC console:
- Sign in to app.osaas.io.
- Go to Settings in the left sidebar.
- Click the { } API tab.
- Copy the personal access token.
Store it in an environment variable. Never commit it to source control.
export OSC_ACCESS_TOKEN=<your-pat>Each service you have subscribed to exposes its own orchestrator base URL (apiUrl). Fetch your subscriptions list from the catalog:
curl -s https://catalog.svc.prod.osaas.io/mysubscriptions \
-H "x-pat-jwt: Bearer $OSC_ACCESS_TOKEN" \
-H "Content-Type: application/json"Response is an array of service entries:
[
{
"serviceId": "eyevinn-test-adserver",
"apiUrl": "https://eyevinn-test-adserver.auto.prod.osaas.io",
"serviceType": "instance"
}
]Find the entry whose serviceId matches the service you want to manage. The apiUrl is your base URL for all create/get/list/delete operations on that service.
If the service is not in the list, you have not subscribed to it yet. Subscribe by sending a POST to
/mysubscriptions:curl -s -X POST https://catalog.svc.prod.osaas.io/mysubscriptions \ -H "x-pat-jwt: Bearer $OSC_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"services":["eyevinn-test-adserver"]}'
All service orchestrator endpoints are protected by a SAT scoped to the specific service. Exchange your PAT for a SAT at the token service:
curl -s -X POST https://token.svc.prod.osaas.io/servicetoken \
-H "x-pat-jwt: Bearer $OSC_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"serviceId":"eyevinn-test-adserver"}'Response:
{
"serviceId": "eyevinn-test-adserver",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiry": 1757123456
}The token field is your SAT. The expiry field is a Unix timestamp. SATs expire after one hour. Refresh by repeating this step.
Use the SAT in the x-jwt header (the service orchestrator uses x-jwt, not Authorization) for all instance operations.
SAT=<token-from-step-3>
API_URL=<apiUrl-from-step-2>
curl -s -X POST "$API_URL" \
-H "x-jwt: Bearer $SAT" \
-H "Content-Type: application/json" \
-d '{"name":"myadserver"}'Response includes the instance URL and any service-specific fields.
Instance names must be lowercase alphanumeric only (
a-z,0-9) — no hyphens, underscores, spaces, or uppercase letters.
curl -s "$API_URL" \
-H "x-jwt: Bearer $SAT" \
-H "Content-Type: application/json"curl -s "$API_URL/myadserver" \
-H "x-jwt: Bearer $SAT" \
-H "Content-Type: application/json"Returns 404 if the instance does not exist.
curl -s -X DELETE "$API_URL/myadserver" \
-H "x-jwt: Bearer $SAT"@osaas/client-core implements this contract. Use it if you are working in Node.js:
import { Context, createInstance, getInstance, listInstances, removeInstance } from "@osaas/client-core";
// OSC_ACCESS_TOKEN env var is read automatically
const ctx = new Context();
// Step 3: get a SAT (Steps 1+2 are handled internally)
const sat = await ctx.getServiceAccessToken("eyevinn-test-adserver");
// Create
const instance = await createInstance(ctx, "eyevinn-test-adserver", sat, {
name: "myadserver"
});
console.log(instance.url);
// Get
const found = await getInstance(ctx, "eyevinn-test-adserver", "myadserver", sat);
// List
const all = await listInstances(ctx, "eyevinn-test-adserver", sat);
// Delete
const result = await removeInstance(ctx, "eyevinn-test-adserver", "myadserver", sat);
// result === 'success' | 'alreadyAbsent'My Apps (custom code deployed via web-runner, python-runner, wasm-runner, or golang-runner) receive a runner refresh token, not a PAT, in the OSC_ACCESS_TOKEN environment variable. To call a catalog service instance from inside a My App, exchange the runner token for a PAT first.
# Inside My App code, OSC_ACCESS_TOKEN holds a runner refresh token
curl -s -X POST https://token.svc.prod.osaas.io/runner-token/refresh \
-H "Content-Type: application/json" \
-d "{\"token\":\"$OSC_ACCESS_TOKEN\"}"Response:
{
"token": "<short-lived-PAT>",
"expiresIn": 3600
}PAT=<token-from-step-A>
curl -s -X POST https://token.svc.prod.osaas.io/servicetoken \
-H "x-pat-jwt: Bearer $PAT" \
-H "Content-Type: application/json" \
-d '{"serviceId":"eyevinn-app-config-svc"}'curl -s "$API_URL/myinstance" \
-H "x-jwt: Bearer $SAT"
OSC_ACCESS_TOKENinside a My App is a runner refresh token, NOT a PAT. Passing it directly to/servicetokenreturns a 401. Always exchange it via/runner-token/refreshfirst.
// Exchange runner token for a PAT
const patRes = await fetch("https://token.svc.prod.osaas.io/runner-token/refresh", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token: process.env.OSC_ACCESS_TOKEN })
});
const { token: pat } = await patRes.json();
// Exchange PAT for a SAT
const satRes = await fetch("https://token.svc.prod.osaas.io/servicetoken", {
method: "POST",
headers: {
"x-pat-jwt": `Bearer ${pat}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ serviceId: "eyevinn-app-config-svc" })
});
const { token: sat } = await satRes.json();
// Call the service
const data = await fetch(`${apiUrl}/myinstance`, {
headers: { "x-jwt": `Bearer ${sat}` }
});| Token | Lifetime | How to refresh |
|---|---|---|
| PAT (console-generated) | Long-lived | Rotate manually in Settings / API |
| PAT (from runner-token/refresh) | 3600 seconds | Repeat Step A |
| SAT | ~1 hour | Repeat Step 3 (or Steps A+B from My Apps) |
A practical strategy for long-running processes is to refresh every 50 minutes or to catch any 401 response from the service and retry once with a fresh SAT.
The complete TypeScript source is in the @osaas/client-core package:
-
packages/core/src/context.ts—Context.getServiceAccessToken(),activateService() -
packages/core/src/core.ts—createInstance(),getInstance(),listInstances(),removeInstance() -
packages/core/src/myapp.ts— My App management via deploy-manager
See also:
- Developer Guide: Service Access Tokens — SAT mechanics and expiry details
- Developer Guide: Service-to-Service Integration — My App calling catalog services
- Developer Guide: OSC CLI in CI Pipelines — using the CLI in automated pipelines