API Authentication¶
Every request to the Manifest Platform API must be authenticated. This page covers all supported authentication methods, including code examples for each.
Authentication Methods¶
The API supports three authentication methods. Choose the one that fits your use case:
| Method | Best For | Header |
|---|---|---|
| API Key | Server-to-server, CI/CD, scripts | X-API-Key |
| Bearer Token (JWT) | Short-lived sessions, OAuth flows | Authorization: Bearer <token> |
| OAuth 2.0 | Third-party integrations, user-delegated access | Standard OAuth headers |
Resolution order
If both X-API-Key and Authorization: Bearer are present, the API key takes precedence.
API Key Authentication¶
API keys are the simplest authentication method. Each key is scoped to an organization and inherits the permissions of the user who created it.
Creating an API Key¶
- Log in to the Manifest Platform web console.
- Navigate to Admin > Settings > API Keys.
- Click Create API Key and give it a descriptive name.
- Copy the key immediately -- it is shown only once.
Using an API Key¶
Pass the key in the X-API-Key header:
Keep your API key secret
Never commit API keys to version control, embed them in client-side code, or share them in plaintext. Use environment variables or a secret manager.
API Key Prefixes¶
All Manifest Platform API keys start with a recognizable prefix:
| Prefix | Type |
|---|---|
fl_ |
Standard organization API key |
fl_hsl_ |
Hosted Service Layer service token |
fl_deploy_ |
Deployment-scoped token |
Bearer Token Authentication¶
Bearer tokens are short-lived JWTs issued by the platform's authentication service. They are used for browser-based sessions and OAuth flows.
Obtaining a Token¶
Exchange credentials for a token using the login endpoint:
curl -X POST \
-H "Content-Type: application/json" \
-d '{"email": "jane@acme.com", "password": "..."}' \
https://api.flow.marut.cloud/api/v1/auth/login
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}
Using a Bearer Token¶
Pass the token in the Authorization header:
Token Refresh¶
Access tokens expire after the duration specified in expires_in (default: 3600 seconds). Use the refresh token to obtain a new access token without re-authenticating:
curl -X POST \
-H "Content-Type: application/json" \
-d '{"refresh_token": "eyJhbGciOiJSUzI1NiIs..."}' \
https://api.flow.marut.cloud/api/v1/auth/refresh
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...(new)...",
"refresh_token": "eyJhbGciOiJSUzI1NiIs...(new)...",
"token_type": "Bearer",
"expires_in": 3600
}
Refresh token rotation
Each refresh request returns a new refresh token and invalidates the previous one. Store the new refresh token for subsequent refreshes.
Handling Expiry in Code¶
import time
import requests
class FlowAuth:
def __init__(self, base_url: str, email: str, password: str):
self.base_url = base_url
self.access_token = None
self.refresh_token = None
self.expires_at = 0
self._login(email, password)
def _login(self, email: str, password: str):
resp = requests.post(
f"{self.base_url}/api/v1/auth/login",
json={"email": email, "password": password},
)
self._store_tokens(resp.json())
def _store_tokens(self, data: dict):
self.access_token = data["access_token"]
self.refresh_token = data["refresh_token"]
self.expires_at = time.time() + data["expires_in"] - 60 # 60s buffer
def get_token(self) -> str:
if time.time() >= self.expires_at:
resp = requests.post(
f"{self.base_url}/api/v1/auth/refresh",
json={"refresh_token": self.refresh_token},
)
self._store_tokens(resp.json())
return self.access_token
def headers(self) -> dict:
return {"Authorization": f"Bearer {self.get_token()}"}
class FlowAuth {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.accessToken = null;
this.refreshToken = null;
this.expiresAt = 0;
}
async login(email, password) {
const resp = await fetch(`${this.baseUrl}/api/v1/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
this.storeTokens(await resp.json());
}
storeTokens(data) {
this.accessToken = data.access_token;
this.refreshToken = data.refresh_token;
this.expiresAt = Date.now() + (data.expires_in - 60) * 1000;
}
async getToken() {
if (Date.now() >= this.expiresAt) {
const resp = await fetch(`${this.baseUrl}/api/v1/auth/refresh`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh_token: this.refreshToken }),
});
this.storeTokens(await resp.json());
}
return this.accessToken;
}
}
OAuth 2.0 Flow¶
For third-party applications that need user-delegated access, the platform supports the OAuth 2.0 Authorization Code flow with PKCE.
Flow Overview¶
sequenceDiagram
participant App as Your Application
participant Browser as User's Browser
participant Auth as Flow Auth Server
participant API as Flow API
App->>App: Generate code_verifier + code_challenge
App->>Browser: Redirect to /oauth/authorize
Browser->>Auth: User logs in & consents
Auth->>App: Redirect with authorization_code
App->>Auth: POST /oauth/token (code + code_verifier)
Auth-->>App: access_token + refresh_token
App->>API: API calls with Bearer token
Step 1: Authorization Request¶
Redirect the user to the authorization endpoint:
https://api.flow.marut.cloud/oauth/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=https://yourapp.com/callback&
scope=read:solutions write:components&
state=random_csrf_token&
code_challenge=BASE64URL_ENCODED_CHALLENGE&
code_challenge_method=S256
Step 2: Token Exchange¶
After the user authorizes, exchange the code for tokens:
curl -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id=YOUR_CLIENT_ID" \
-d "code=AUTH_CODE_FROM_CALLBACK" \
-d "redirect_uri=https://yourapp.com/callback" \
-d "code_verifier=YOUR_CODE_VERIFIER" \
https://api.flow.marut.cloud/oauth/token
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read:solutions write:components"
}
Available Scopes¶
| Scope | Description |
|---|---|
read:solutions |
List and read solutions |
write:solutions |
Create, update, and delete solutions |
read:components |
List and read components |
write:components |
Create, update, and delete components |
read:agents |
List and read agents |
execute:agents |
Run agents |
read:workflows |
List and read workflows |
execute:workflows |
Run workflows |
read:connectors |
List connectors and instances |
write:connectors |
Create and configure connector instances |
read:datasets |
Query datasets |
write:datasets |
Create and modify datasets |
admin:org |
Organization management (users, settings, billing) |
HSL Service Tokens¶
Hosted Service Layer (HSL) service tokens provide authentication for deployed hosted services. These are minted by the platform and scoped to a specific service.
Minting an HSL Token¶
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"claims":{"sub":"service-caller"},"ttl_minutes":60}' \
"https://api.flow.marut.cloud/api/v1/orgs/$ORG_ID/workspaces/$WORKSPACE_ID/hosted-services/$SERVICE_ID/service-tokens/mint"
Response:
{
"token": "fl_hsl_abc123...",
"service_id": "550e8400-e29b-41d4-a716-446655440000",
"expires_at": "2026-04-21T00:00:00Z"
}
Canonical token minting path
Use the tenant-scoped endpoint POST /api/v1/orgs/{org_id}/workspaces/{workspace_id}/hosted-services/{service_id}/service-tokens/mint.
CLI equivalent: flow-sdk hosted-services mint-token --service-id <service_id>.
Request-Bound CLI Login¶
User CLI login uses a request-bound browser flow.
- CLI creates request:
- Browser validates request (session-authenticated):
- Browser issues one-time token (session-authenticated):
POST /api/v1/auth/cli-login-requests/{request_id}/token
Content-Type: application/json
{"secret":"<opaque-secret>"}
Profile guardrail:
- Token issuance requires user setting
allow_cli_login_access=true. - Users manage this from Profile -> Access via the
Allow CLI login accesstoggle.
CLI command:
Verifying Credentials¶
Test that your credentials are valid:
Response:
{
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"email": "jane@acme.com",
"organization": {
"id": "org-uuid",
"name": "Acme Corp"
},
"role": "solution_builder",
"scopes": ["read:solutions", "write:components", "execute:agents"]
}
Next Steps¶
- API Overview -- Base URL, error format, rate limits
- Endpoints -- Complete endpoint catalog