Access Tokens (Fine-Grained)
A fine-grained access token is a key that opens only the doors you choose. You decide up front what the token may do (for example "manage one app and nothing else"), and you can change that later without handing out a new token. If a CI pipeline leaks a fine-grained token, the damage is limited to what its policy allows, not your whole workspace.
Which token do I need?
| What you need | Use this |
|---|---|
| A script, CI job or AI client that should do only a few specific things | Fine-grained access token (this page) |
| Full access to your workspace for a trusted tool | Personal access token (PAT), created in the same API Access page |
| Calling a running service instance from outside OSC | Service access token, see Service Access Tokens |
| Settings and passwords for your own app | Parameter Store, see Credentials and Secrets |
Your existing personal access tokens are not affected and keep working exactly as before. Fine-grained tokens are a separate kind of token that you opt into.
How it works
The token itself only says who it belongs to (your workspace, your user and a token id). It does not say what it may do. The list of allowed actions, called the policy, is stored by OSC and looked up on every request. That is why you can edit a policy and have it apply to a token that is already in use.
A policy is a list of statements. Each statement names some actions and the resources they apply to. A request is allowed only if some statement matches both. Everything else is denied, so there is no way to write a "deny" rule and nothing is allowed by accident.
{
"version": 1,
"statements": [
{ "actions": ["myapps:read", "myapps:update"], "resources": ["myapps/webshop"] }
]
}
An action looks like myapps:update: the area, a colon, then what is done. A resource looks like myapps/webshop: the area, a slash, then the name. A * at the end of a resource matches any name that starts the same way, so myapps/web-* covers web-api and web-front. Your workspace is never written in a resource, because the token is always tied to the workspace that created it.
A few facts worth knowing before you start:
- A policy change applies within about 30 seconds. Requests in that window may still use the previous version.
- The expiry date is fixed when you create the token (at most 90 days) and cannot be changed. To extend a token's life, create a new one.
- Each workspace can have up to 100 fine-grained tokens, and a policy can hold up to 64 statements and 8 KB.
- Managing tokens, billing changes and workspace members are not part of any template. You can only grant them by writing them into a statement yourself.
Quickstart: a CI token that can only manage one app
Goal: give a CI job the right to read and update the app webshop (updating includes restarting it), and nothing else.
- Open app.osaas.io/dashboard/profile/api (API Access in the profile menu). Under Access tokens, click Create token.
- Enter the name
ci-webshop, and optionally a description. In Expires in (days) enter30. - Under Template choose Custom policy. In the Guided tab fill in one statement. Set Actions to
myapps:read, myapps:updateand Resources tomyapps/webshop. (The JSON tab shows the same policy as the example above.) - Click Create. The next screen, Token Created Successfully, shows the token once. Copy it now and store it in your CI system's secret storage. It cannot be shown again.
- Open the new token from the list and find Test this policy. Enter the action
myapps:updateand the resourcemyapps/webshop, then click Check. You should see Allowed. Trymyapps:deleteon the same resource, and you should see Denied. - Use the token like a PAT. It is sent in the same
x-pat-jwtheader:
export TOKEN=<the token you copied>
# Allowed: the policy covers myapps:read on webshop
curl -s -o /dev/null -w "%{http_code}\n" \
-H "x-pat-jwt: Bearer $TOKEN" \
https://deploy.svc.prod.osaas.io/myapps/webshop/agentic-sdlc
# Not allowed: the policy says nothing about another app
curl -s -H "x-pat-jwt: Bearer $TOKEN" \
https://deploy.svc.prod.osaas.io/myapps/otherapp/agentic-sdlc
The first call returns status 200 with a short JSON answer about the app. The second returns status 403 with this body:
{ "reason": "no_matching_statement", "code": "insufficient_scope" }
Known limitation: reading the full details of an app (GET /myapps/<app>) and the app list with a fine-grained token is not supported yet and returns an error even when the policy allows it. Use the calls shown here, or a personal access token, until this is fixed.
- Later, to also let the job deploy, open the token, click Edit policy, add
myapps:deployto the actions and click Save. The same token gets the new permission within about 30 seconds. Nothing has to be changed in your CI system.
Templates
When you create a token you can start from a template instead of writing a policy. A template is copied into the token at creation, so changing OSC's templates later never changes your existing tokens.
| Template | What it allows |
|---|---|
| Viewer | Read-only access |
| Deployer | Read, and deploy apps, pages and instances |
| CI publisher | Publish and restart apps and pages from CI |
| Agent runner | Run agent tasks and jobs |
| MCP read-only | Read-only access for MCP clients |
| Full | Everything except managing tokens, billing and members. Marked Broad access in the form |
Pick the narrowest template that works, or choose Custom policy for something more precise.
Managing tokens
The Access tokens list shows each token's name, status, policy version, when it was last used and when it expires. Workspace owners and admins see and manage every token in the workspace. Other members see and manage the tokens they created.
The status is one of Active, Expires Soon (less than 14 days left), Disabled or Expired. The Last used time is updated about every 5 minutes, so it can lag behind.
Edit scope. Open a token and use Edit policy. The page shows which version you are editing. If someone else changed the token while you were editing, nothing is saved and you are offered Load current version so you can reapply your change. You can also rename the token and change its description with Save details.
Disable and enable. Disabling is reversible. Requests with a disabled token are rejected within about 30 seconds, and Enable turns it back on. An expired token cannot be enabled.
Delete. Deleting is permanent and anything using the token stops working.
When a request is denied
A fine-grained token that tries something its policy does not allow gets status 403 and a body with "code": "insufficient_scope". The reason says why:
| reason | Meaning |
|---|---|
no_matching_statement |
No statement allows this action on this resource. Add one, or use a different token |
undeclared_route |
The call is not available to fine-grained tokens |
token_disabled |
The token is disabled |
policy_unavailable |
OSC could not load the policy right now, so the request was refused to be safe. Try again shortly |
The insufficient_scope answer only exists for fine-grained tokens. Personal access tokens never receive it and behave as they always have.
Activity and the access log
Each token has an Activity tab with three parts: Policy history (who changed the policy and when), Usage (requests per hour and action, for the last 24 hours or 7 days) and Recent denies (rejected requests, grouped by minute with a count). Usage figures can be about 5 minutes behind.
For the whole picture, open View access log from the Access tokens section. It lists every token change and every allow or deny decision in the workspace, or only your own tokens if you are not an owner or admin. You can filter by token, user, event, action, decision and a UTC time range, and open a row for details such as the source IP address and policy version.
To keep a copy, use the export button in the access log. Choose CSV for spreadsheets or NDJSON (one JSON event per line) for full detail. Each file holds up to 500,000 events, and ranges longer than 31 days download in several parts. Every export is itself recorded in the log. Events are kept for 365 days.
Using access tokens from the CLI, Terraform and AI tools
| Tool | Status |
|---|---|
| Web console | Available, as described above |
| MCP tools for AI clients | Released in osaas-ai v8.29.0: create-access-token, list-access-tokens, get-access-token, replace-access-token-policy, update-access-token, delete-access-token, simulate-access-token and list-access-token-audit-events |
osc token in the CLI |
Not yet available |
Terraform resource osc_access_token |
Not yet available |
When an AI client creates a token, the token value is never printed in the chat. You get a short-lived download link instead.
What to do next
- Create a token for one real job and use Check to confirm it can do what you expect and nothing more.
- Look at the Activity tab after the first run and tighten the policy if the token has more access than the job needs.
- Replace long-lived personal access tokens in CI with fine-grained ones when you can.
Glossary
- Policy: the list of things a token is allowed to do, stored by OSC.
- Statement: one entry in a policy, pairing actions with resources.
- Action: a single operation such as
myapps:update. - Resource: the thing an action applies to, such as
myapps/webshop. - Deny by default: anything not allowed by a statement is refused.
- PAT (personal access token): a token with full access to your workspace.
- JWT: the signed text format the token is written in. You treat it as an opaque string.