# Token Management

> Refresh, introspect, and manage OAuth tokens

- **URL**: https://orshot.com/docs/developers/token-management

---

After a user authorizes your app, you receive an access token and (optionally) a refresh token. Here's how to manage them.

## Token Types

| Token              | Prefix | Lifetime   | Purpose                            |
| ------------------ | ------ | ---------- | ---------------------------------- |
| Access token       | `ost_` | 15 minutes | Authenticate API requests          |
| Refresh token      | `osr_` | 30 days    | Get new access tokens              |
| Authorization code | `osc_` | 5 minutes  | One-time use, exchanged for tokens |

## Using Access Tokens

Include the access token in the `Authorization` header of every API request:```bash
curl https://api.orshot.com/v1/templates \
  -H "Authorization: Bearer ost_your_access_token"
```## Refreshing Tokens

Access tokens expire after 15 minutes. Use the refresh token to get a new one without re-prompting the user:```bash
curl -X POST https://api.orshot.com/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "refresh_token",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "refresh_token": "osr_your_refresh_token"
  }'
```**Response:**```json
{
  "access_token": "ost_new_access_token",
  "refresh_token": "osr_new_refresh_token",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "workspace:read render:generate",
  "user_id": "user-uuid",
  "workspace_ids": ["workspace-uuid-1"]
}
```## Token Introspection

Check whether a token is still valid and see its metadata:```bash
curl -X POST https://api.orshot.com/v1/oauth/introspect \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "token": "ost_token_to_check"
  }'
```**Active token response:**```json
{
  "active": true,
  "scope": "workspace:read render:generate",
  "client_id": "your-client-id",
  "user_id": "user-uuid",
  "workspace_ids": ["workspace-uuid-1"],
  "exp": 1714500000
}
```**Expired or revoked token:**```json
{
  "active": false
}
```## Best Practices

### Handle token expiry gracefully

Don't wait for a `401` response to refresh. Check the `expires_in` value and refresh proactively:```javascript
function isTokenExpiringSoon(expiresAt, bufferSeconds = 60) {
  return Date.now() / 1000 > expiresAt - bufferSeconds;
}
```### Store tokens securely

- **Server-side apps**: Store tokens in an encrypted database or secrets manager
- **Desktop apps**: Use the OS keychain (macOS Keychain, Windows Credential Manager)
- **CLI tools**: Store in a local config file with restricted file permissions (`chmod 600`)
- **Never** store tokens in localStorage, cookies without `httpOnly`, or source code

### Handle refresh failures

If a refresh request fails, the user needs to re-authorize:```javascript
async function getValidToken(storedTokens) {
  if (!isTokenExpiringSoon(storedTokens.expiresAt)) {
    return storedTokens.accessToken;
  }

  try {
    const response = await refreshToken(storedTokens.refreshToken);
    // Map OAuth response (snake_case) to your stored format
    const newTokens = {
      accessToken: response.access_token,
      refreshToken: response.refresh_token,
      expiresAt: Date.now() / 1000 + response.expires_in,
    };
    await saveTokens(newTokens);
    return newTokens.accessToken;
  } catch (err) {
    // Refresh failed — re-authorize
    throw new Error("Re-authorization required");
  }
}
```## Token Revocation

Tokens are automatically revoked when:

- The user removes your app's access from their Orshot settings
- The user removes a workspace from your app's grant (only tokens for that workspace)
- Your app's OAuth client is disabled
- A refresh token is replaced during rotation (the old one is invalidated)

## Sandbox Restrictions

If an app is in [development mode](https://orshot.com/docs/developers/register-app#development-mode) (not yet published), tokens are restricted at the API level:

- Only tokens belonging to the **app owner** or **test users** will work
- API calls from other users return a `sandbox_restricted` error with HTTP 403
- This applies even if a token was issued while the app was published — switching an app back to private immediately enforces sandbox restrictions on existing tokens```json
{
  "error": "This app is in development mode. Only the developer or approved test users can make API calls.",
  "code": "sandbox_restricted"
}
```To proactively revoke access from your side, stop using the tokens and discard them. There is no explicit revocation endpoint at this time.