Authentication
PortEden uses Access Tokens (API keys) for programmatic access. There are two ways to obtain a key: the Browser Login Flow for CLI tools and SDKs, or Direct Key Generation from the web dashboard.
Base URL
/api/auth/tokenBrowser Login Flow
A three-step flow for CLI tools and SDKs: initiate a session, open the browser for authentication, then poll for the API key.
Step 1: Initiate Login
/api/auth/token/loginNo AuthInitiate Login
Creates a login session and returns a URL for the user to open in their browser.
Rate limit: 10 req/min, 30 req/hour
Request Body (optional)
| Field | Type | Required | Description |
|---|---|---|---|
| operatorId | UUID | No | Target operator/workspace ID. If omitted, uses user's default operator |
| keyTitle | string | No | Friendly name for the generated key |
{ "operatorId": "550e8400-e29b-41d4-a716-446655440000", "keyTitle": "My CLI Key" } Response Fields
| Field | Type | Description |
|---|---|---|
| sessionToken | string | Session identifier for polling |
| pollSecret | string | Secret for PKCE-like verification when polling. Keep this client-side only |
| loginUrl | string | URL to open in the user's browser for authentication |
| expiresAt | DateTime | When this session expires |
| message | string | Human-readable instructions |
Step 2: Poll for Completion
/api/auth/token/poll/{sessionToken}?secret={pollSecret}No AuthPoll for Completion
Client polls this endpoint to check if the user has completed browser authentication.
Rate limit: 60 req/min (allows ~1/sec polling), 300 req/hour
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
| sessionToken | Path | string | Session token from Step 1 |
| secret | Query | string | Poll secret from Step 1 (PKCE-like verification) |
Response 200 OK
{ "status": "completed", "apiKey": "pe_k1_abc123..." } Status Values
| Status | Meaning | Action |
|---|---|---|
| pending | User hasn't authenticated yet | Continue polling |
| completed | Authentication successful | Extract apiKey and stop polling |
| expired | Session timed out | Start a new login flow |
| invalid_secret | Wrong poll secret (returned as 400) | Verify the secret matches Step 1 |
Important
apiKey is only returned once when status is completed. Store it securely — it cannot be retrieved again.Step 3: Web App Callback (Internal)
/api/auth/token/callbackJWT SessionWeb App Callback
Called by the PortEden web app after the user authenticates in the browser. Not called by external clients directly.
Note
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| sessionToken | string | Yes | Session token being completed |
| operatorId | UUID | No | Target operator. Falls back to user's default |
| keyId | int | No | Existing key ID to link (must provide with apiKey) |
| apiKey | string | No | Existing API key to link (must provide with keyId) |
Response 200 OK
{ "success": true, "keyId": 42, "keyTitle": "My CLI Key", "message": "Authentication successful. You can close this browser window." } Direct Key Generation
/api/auth/token/generateJWT SessionGenerate API Key
Generates an API key directly for authenticated web users — no browser flow needed.
Rate limit: 10 keys per hour
Request Body (all optional)
| Field | Type | Default | Description |
|---|---|---|---|
| operatorId | UUID | User's default | Target operator/workspace |
| title | string | Auto-generated | Friendly name for the key |
| avatar | string | "general" | Key avatar identifier |
| expiresInDays | int | Never | Key expiration in days from now |
| masterAccessLevel | string | "full_access" | Access level |
| visibleFields | string[] | All fields | Fields visible when masterAccessLevel is view_filtered |
| allowedOperations | string[] | All operations | Allowed write operations when masterAccessLevel is full_access |
| timeframePastDays | int | Unlimited | How many days into the past the token can access |
| timeframeFutureDays | int | Unlimited | How many days into the future the token can access |
{ "operatorId": "550e8400-e29b-41d4-a716-446655440000", "title": "Production Integration Key", "avatar": "claude", "expiresInDays": 90, "masterAccessLevel": "view_only", "visibleFields": ["title", "times", "attendees", "location"], "allowedOperations": ["respond_to_event"], "timeframePastDays": 30, "timeframeFutureDays": 60 } Important
apiKey value is returned only once at creation time. It cannot be retrieved again. Store it securely.Using the API Key
Once you have an API key, include it in all requests as a Bearer token:
Authorization: Bearer pe_k1_abc123def456... Or set the environment variable for CLI/SDK usage:
export PE_API_KEY=pe_k1_abc123def456... Token Status
/api/auth/token/statusBearer TokenGet Token Status
Returns information about the current access token and authenticated user.
Response 200 OK
{ "authenticated": true, "userId": 123, "email": "user@example.com", "userName": "Jane Smith", "accountId": "550e8400-e29b-41d4-a716-446655440000", "operatorId": "660e8400-e29b-41d4-a716-446655440000", "operatorName": "Acme Corp", "keyId": 42, "keyTitle": "My Integration Key", "keyAvatar": "claude", "createdAt": "2026-01-15T10:00:00Z", "expiresAt": "2026-04-15T10:00:00Z", "lastUsedAt": "2026-02-07T09:30:00Z", "masterAccessLevel": "full_access", "visibleFields": ["title", "times", "attendees", "location", "description", "status", "labels", "join_url", "organizer"], "allowedOperations": ["respond_to_event", "edit_title", "edit_location", "edit_description", "edit_attendees", "edit_times", "create_events", "delete_events"], "timeframePastDays": null, "timeframeFutureDays": null, "accessRules": [], "linkedResources": [ { "resourceId": "aaa-bbb-ccc", "title": "Primary Calendar" } ], "hasLinkedResources": true }