---
title: "Authentication"
description: "AINative SSO, email/password, API keys, and zero-friction agent tokens for OpenCap Stack"
canonical: "https://docs.ainative.studio/docs/opencapstack/authentication"
last-updated: "2026-10-03T21:47:10.764Z"
---

# Authentication

Source: https://docs.ainative.studio/docs/opencapstack/authentication

> AINative SSO, email/password, API keys, and zero-friction agent tokens for OpenCap Stack

## Authentication

OpenCap Stack supports three authentication methods. Choose the one that fits your use case.

| Method | Best For | Token Lifetime |
|--------|----------|---------------|
| [AINative SSO](#ainative-sso) | Users who already have an AINative account | 7 days (refresh) |
| [Email / Password](#email--password) | Standard login, your own user base | 7 days (refresh) |
| [Agent Onboarding](#agent-onboarding) | AI agents, CI/CD, server-to-server | Non-expiring API key |

All methods return a **JWT Bearer token** used in the `Authorization` header:

```
Authorization: Bearer <your-token>
```

---

## AINative SSO

If your users have AINative accounts, you can authenticate them against OpenCap Stack without a separate credential.

### Server-Side Flow (Recommended)

Send the user's AINative credentials to OpenCap Stack's proxy endpoint — no redirect required:

```bash
curl -X POST https://api.opencapstack.com/api/v1/auth/ainative-login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "ainative-password"
  }'
```

**Response:**
```json
{
  "token": "eyJhbGci...",
  "refreshToken": "eyJhbGci...",
  "user": {
    "id": "usr_abc123",
    "email": "user@example.com",
    "name": "Jane Doe",
    "companyId": "co_xyz789"
  }
}
```

This endpoint:
1. Validates the credentials against the AINative `/api/v1/auth/login` API
2. Fetches the AINative user profile via `/api/v1/auth/me`
3. Provisions (or retrieves) a matching OpenCap Stack user
4. Returns an OpenCap Stack JWT

### Using the Token

```javascript
const response = await fetch('https://api.opencapstack.com/api/v1/auth/ainative-login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email, password }),
});

const { token } = await response.json();

// Store and use for subsequent requests
const data = await fetch('https://api.opencapstack.com/api/v1/stakeholders', {
  headers: { Authorization: `Bearer ${token}` },
});
```

### Next.js / React Integration

```javascript
// lib/authService.js
export async function loginWithAINative(email, password) {
  const res = await fetch('/api/v1/auth/ainative-login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, password }),
  });

  if (!res.ok) throw new Error('Authentication failed');
  
  const { token, user } = await res.json();
  
  // Set cookie for Edge Middleware auth
  document.cookie = `session=${token}; path=/; secure; samesite=strict`;
  
  return { token, user };
}
```

:::note AINative OAuth 2.1 (Browser Redirect)
The full OAuth 2.1 PKCE browser redirect flow (`/oauth/authorize`) is available for registered redirect URIs. Contact AINative to add your domain to the allowlist. For most integrations the server-side proxy above is simpler and works immediately.
:::

---

## Email / Password

Standard registration and login for users with their own OpenCap Stack accounts.

### Register

```bash
curl -X POST https://api.opencapstack.com/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jane Doe",
    "email": "jane@startup.com",
    "password": "securepassword123"
  }'
```

**Response:**
```json
{
  "token": "eyJhbGci...",
  "refreshToken": "eyJhbGci...",
  "user": {
    "id": "usr_abc123",
    "email": "jane@startup.com",
    "name": "Jane Doe"
  }
}
```

### Login

```bash
curl -X POST https://api.opencapstack.com/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@startup.com",
    "password": "securepassword123"
  }'
```

### Refresh Token

JWTs expire after 7 days. Use the refresh token to get a new one without re-authenticating:

```bash
curl -X POST https://api.opencapstack.com/api/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{ "refreshToken": "eyJhbGci..." }'
```

---

## Agent Onboarding

The fastest path for AI agents, automation scripts, and server-to-server integrations. **Zero human steps required.**

```bash
curl -X POST https://api.opencapstack.com/api/v1/agents/onboard \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "my-cap-table-agent",
    "agent_name": "Cap Table Automation",
    "capabilities": [
      "read:cap-table",
      "write:stakeholders",
      "write:equity",
      "read:financials"
    ]
  }'
```

**Response:**
```json
{
  "token": "eyJhbGci...",
  "api_key": "ocs_live_abc123xyz...",
  "company_id": "co_xyz789",
  "agent_id": "my-cap-table-agent",
  "capabilities": ["read:cap-table", "write:stakeholders", "write:equity", "read:financials"],
  "provisioned_at": "2026-05-13T00:00:00Z"
}
```

Store both the `token` (JWT) and `api_key` — the API key doesn't expire and can be used directly.

### Available Capability Scopes

| Scope | Access |
|-------|--------|
| `read:cap-table` | Stakeholders, share classes, equity grants, ownership percentages |
| `write:stakeholders` | Create, update, delete stakeholders |
| `write:equity` | Issue grants, update vesting schedules, create share classes |
| `read:financials` | 409A valuations, financial reports, fundraising data |
| `write:documents` | Upload and manage legal documents |
| `read:analytics` | Reporting, dilution models, waterfall analysis |
| `write:safe-notes` | Create and update SAFE instruments |
| `admin` | Full access — use only for trusted internal agents |

### Using the API Key

```bash
## API key works as a Bearer token
curl https://api.opencapstack.com/api/v1/stakeholders \
  -H "Authorization: Bearer ocs_live_abc123xyz..."
```

```python
import requests

API_KEY = "ocs_live_abc123xyz..."
BASE_URL = "https://api.opencapstack.com/api/v1"

headers = {"Authorization": f"Bearer {API_KEY}"}

stakeholders = requests.get(f"{BASE_URL}/stakeholders", headers=headers).json()
```

---

## Bring Your Own Auth

If you're self-hosting OpenCap Stack, you can configure any JWT-compatible identity provider.

Set these environment variables:

```bash
JWT_SECRET=your-256-bit-secret
JWT_ISSUER=https://your-auth-provider.com    # optional
JWT_AUDIENCE=opencapstack                      # optional
```

Your tokens must be signed RS256 or HS256 JWTs with a `sub` (user ID) claim. The middleware validates the signature and extracts the user from your existing user table.

For OIDC providers (Auth0, Clerk, Supabase Auth):

```bash
OIDC_ISSUER=https://your-tenant.auth0.com/
OIDC_AUDIENCE=https://api.opencapstack.com
```

See the [Self-Hosting guide](./self-hosting) for full configuration.

---

## Token Security

- Store tokens in `httpOnly` cookies or server-side sessions — never `localStorage` for production
- The Next.js Edge Middleware validates the `session` cookie on every protected route
- Refresh tokens are single-use and rotated on each refresh
- Agent API keys can be revoked via `DELETE /api/v1/agents/{agent_id}`
