Repository navigation
Programmatic Instance Management
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:
- Sign in to app.osaas.io
- Click your avatar/email in the top-right corner and select API Access, or go directly to app.osaas.io/dashboard/profile/api
- In Access Tokens, click Create New Token
- Enter a Token Name (Description and Purpose are optional), pick an expiry date, and click Create Token
- Copy the token immediately, you will not be able to see it again
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-se.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, golang-runner, dotnet-runner, or php-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 | Revoke and create a new one on the API Access page |
| 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