Skip to content

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

  1. Log in to the Manifest Platform web console.
  2. Navigate to Admin > Settings > API Keys.
  3. Click Create API Key and give it a descriptive name.
  4. Copy the key immediately -- it is shown only once.

Using an API Key

Pass the key in the X-API-Key header:

curl -X GET \
  -H "X-API-Key: fl_your_api_key_here" \
  -H "Accept: application/json" \
  https://api.flow.marut.cloud/api/v1/solutions
import requests

response = requests.get(
    "https://api.flow.marut.cloud/api/v1/solutions",
    headers={"X-API-Key": "fl_your_api_key_here"},
)
print(response.json())
from flow_sdk import FlowSDK

flow = FlowSDK(
    org_id="org-uuid",
    workspace_id="workspace-uuid",
    auth_token="jwt-token",
    platform_url="https://api.flow.marut.cloud",
)
const response = await fetch(
  "https://api.flow.marut.cloud/api/v1/solutions",
  {
    headers: {
      "X-API-Key": "fl_your_api_key_here",
      "Accept": "application/json",
    },
  }
);
const data = await response.json();

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:

curl -X GET \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  https://api.flow.marut.cloud/api/v1/solutions
import requests

token = "eyJhbGciOiJSUzI1NiIs..."
response = requests.get(
    "https://api.flow.marut.cloud/api/v1/solutions",
    headers={"Authorization": f"Bearer {token}"},
)
import httpx

async with httpx.AsyncClient() as client:
    response = await client.get(
        "https://api.flow.marut.cloud/api/v1/solutions",
        headers={"Authorization": f"Bearer {token}"},
    )

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.

  1. CLI creates request:
POST /api/v1/auth/cli-login-requests
  1. Browser validates request (session-authenticated):
GET /api/v1/auth/cli-login-requests/{request_id}?secret=<opaque-secret>
  1. 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 access toggle.

CLI command:

flow-sdk auth login-user --url <platform-url> [--token <jwt>]

Verifying Credentials

Test that your credentials are valid:

curl -H "X-API-Key: fl_your_api_key_here" \
     https://api.flow.marut.cloud/api/v1/auth/whoami

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