Files
boc/aamos-ai-site/docs/authentication/index.html
T
Bernt 58ca4e68db feat(boc): Complete Business Operations Center v1.0
- Go backend API with full CRUD for all modules (CRM, Sales, Finance, HR, Legal, Marketing, Support, Purchase, Inventory, Projects, Automation, Analytics)
- Rust analytics service with parallel report generation
- C runtime with POSIX shared memory IPC
- PostgreSQL schema with 30+ tables, full migrations
- Redis cache, sessions, pub/sub
- Kafka event streaming with Zookeeper
- WebSocket hub for real-time updates
- Automation engine with cron jobs, workflows, event triggers
- JWT authentication, multi-tenant from start
- Docker Compose with all services
- Nginx reverse proxy with rate limiting
- Integration tests passing
- Feature gap analysis against Fortnox/Odoo/Visma

Refs: BOC-001
2026-07-12 12:41:35 +00:00

312 lines
17 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Authentication — AAMOS API Docs</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--navy: #0A2540; --blue: #635BFF; --blue-light: #7B74FF; --blue-faint: #F0EFFF;
--text: #1a2332; --text-muted: #5a6880; --border: #e2e8f0;
--bg: #ffffff; --bg-sidebar: #f8fafc; --bg-code: #f1f5f9;
--success: #0ea472; --warning: #f59e0b; --danger: #ef4444;
--sidebar-w: 260px; --header-h: 60px;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Inter', sans-serif; color: var(--text); background: var(--bg); font-size: 15px; line-height: 1.6; }
a { color: var(--blue); text-decoration: none; }
a:hover { text-decoration: underline; }
.header {
position: fixed; top: 0; left: 0; right: 0; height: var(--header-h);
background: var(--navy); display: flex; align-items: center;
padding: 0 24px; z-index: 100; gap: 16px;
}
.header-logo { display: flex; align-items: center; gap: 10px; color: #fff; font-weight: 700; font-size: 18px; text-decoration: none; }
.header-logo-mark { width: 32px; height: 32px; background: var(--blue); border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 800; color: #fff; }
.header-badge { background: rgba(99,91,255,0.25); color: #a5a0ff; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 20px; letter-spacing: 0.05em; }
.header-nav { margin-left: auto; display: flex; gap: 24px; align-items: center; }
.header-nav a { color: rgba(255,255,255,0.75); font-size: 14px; font-weight: 500; }
.header-nav a:hover { color: #fff; text-decoration: none; }
.btn-primary { background: var(--blue); color: #fff; padding: 8px 18px; border-radius: 8px; font-size: 13px; font-weight: 600; }
.hamburger { display: none; background: none; border: none; color: #fff; cursor: pointer; padding: 4px; }
.layout { display: flex; margin-top: var(--header-h); min-height: calc(100vh - var(--header-h)); }
.sidebar {
width: var(--sidebar-w); background: var(--bg-sidebar); border-right: 1px solid var(--border);
position: fixed; top: var(--header-h); bottom: 0; overflow-y: auto; padding: 24px 0;
transition: transform 0.25s ease;
}
.sidebar-section { margin-bottom: 8px; }
.sidebar-label { font-size: 11px; font-weight: 700; letter-spacing: 0.08em; color: var(--text-muted); padding: 8px 20px 4px; text-transform: uppercase; }
.sidebar-link { display: flex; align-items: center; gap: 8px; padding: 7px 20px; color: var(--text-muted); font-size: 13.5px; font-weight: 500; transition: all 0.15s; border-left: 3px solid transparent; }
.sidebar-link:hover { color: var(--navy); background: var(--blue-faint); text-decoration: none; }
.sidebar-link.active { color: var(--blue); background: var(--blue-faint); border-left-color: var(--blue); }
.sidebar-link .icon { width: 16px; height: 16px; flex-shrink: 0; opacity: 0.7; }
.main { margin-left: var(--sidebar-w); flex: 1; padding: 48px 56px; max-width: calc(860px + var(--sidebar-w)); }
.page-header { margin-bottom: 40px; }
.breadcrumb { font-size: 13px; color: var(--text-muted); margin-bottom: 12px; }
.breadcrumb a { color: var(--text-muted); }
.breadcrumb a:hover { color: var(--blue); }
h1 { font-size: 32px; font-weight: 800; color: var(--navy); margin-bottom: 12px; }
.page-desc { font-size: 16px; color: var(--text-muted); }
h2 { font-size: 20px; font-weight: 700; color: var(--navy); margin: 36px 0 12px; padding-bottom: 10px; border-bottom: 1px solid var(--border); }
h3 { font-size: 16px; font-weight: 600; color: var(--navy); margin: 20px 0 8px; }
p { color: var(--text-muted); margin-bottom: 12px; }
.code-wrap { position: relative; margin: 12px 0; }
.code-lang { position: absolute; top: 10px; right: 44px; font-size: 11px; font-weight: 600; color: rgba(255,255,255,0.4); text-transform: uppercase; letter-spacing: 0.05em; font-family: 'Inter', sans-serif; }
.copy-btn { position: absolute; top: 8px; right: 10px; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.15); color: rgba(255,255,255,0.7); border-radius: 6px; padding: 4px 8px; font-size: 11px; cursor: pointer; font-family: 'Inter', sans-serif; font-weight: 500; transition: all 0.15s; }
.copy-btn:hover { background: rgba(255,255,255,0.2); color: #fff; }
.copy-btn.copied { color: #4ade80; border-color: #4ade80; }
pre { background: #0f1923; color: #e2e8f0; border-radius: 10px; padding: 18px 20px; overflow-x: auto; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.7; }
code { font-family: 'JetBrains Mono', monospace; }
.inline-code { background: var(--bg-code); color: #c2410c; padding: 2px 6px; border-radius: 4px; font-size: 13px; font-family: 'JetBrains Mono', monospace; }
.tok-kw { color: #c792ea; } .tok-str { color: #c3e88d; } .tok-num { color: #f78c6c; } .tok-cmt { color: #546e7a; }
.tok-url { color: #80cbc4; } .tok-key { color: #89ddff; } .tok-bool { color: #ff5572; }
.tok-method { color: #ffcb6b; font-weight: 600; } .tok-path { color: #80cbc4; }
.info-box { background: var(--blue-faint); border: 1px solid #d4d0ff; border-radius: 10px; padding: 16px 20px; display: flex; gap: 12px; margin: 20px 0; }
.info-box.warning { background: #fffbeb; border-color: #fde68a; }
.info-box.danger { background: #fff5f5; border-color: #fecaca; }
.info-box-icon { font-size: 18px; flex-shrink: 0; }
.info-box-body { font-size: 13.5px; color: var(--text); }
.info-box-body strong { color: var(--navy); }
.divider { border: none; border-top: 1px solid var(--border); margin: 40px 0; }
/* Tier table */
.tier-table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 14px; }
.tier-table th { background: var(--navy); color: #fff; padding: 12px 16px; text-align: left; font-size: 12px; font-weight: 600; letter-spacing: 0.05em; }
.tier-table th:first-child { border-radius: 8px 0 0 0; } .tier-table th:last-child { border-radius: 0 8px 0 0; }
.tier-table td { padding: 12px 16px; border-bottom: 1px solid var(--border); color: var(--text-muted); }
.tier-table tr:hover td { background: var(--bg-sidebar); }
.tier-table tr:last-child td { border-bottom: none; }
.tier-badge { display: inline-block; padding: 2px 10px; border-radius: 20px; font-size: 11px; font-weight: 700; }
.tier-free { background: #dcfce7; color: #15803d; }
.tier-starter { background: #dbeafe; color: #1d4ed8; }
.tier-pro { background: var(--blue-faint); color: var(--blue); }
/* Error table */
.error-table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 14px; }
.error-table th { padding: 10px 14px; text-align: left; font-size: 12px; font-weight: 600; color: var(--text-muted); border-bottom: 2px solid var(--border); }
.error-table td { padding: 12px 14px; border-bottom: 1px solid var(--border); color: var(--text-muted); vertical-align: top; }
.status-badge { display: inline-block; padding: 3px 10px; border-radius: 6px; font-family: 'JetBrains Mono', monospace; font-size: 12px; font-weight: 700; }
.s-401 { background: #fff5f5; color: #dc2626; }
.s-403 { background: #fff7ed; color: #d97706; }
.s-429 { background: #faf5ff; color: #7c3aed; }
.s-500 { background: #f1f5f9; color: #475569; }
@media (max-width: 768px) {
.sidebar { transform: translateX(-100%); z-index: 50; }
.sidebar.open { transform: translateX(0); }
.main { margin-left: 0; padding: 24px 20px; }
.hamburger { display: flex; }
.header-nav { display: none; }
.overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 40; }
.overlay.visible { display: block; }
}
</style>
</head>
<body>
<header class="header">
<button class="hamburger" id="hamburger" aria-label="Menu">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none"><rect y="4" width="22" height="2" rx="1" fill="white"/><rect y="10" width="22" height="2" rx="1" fill="white"/><rect y="16" width="22" height="2" rx="1" fill="white"/></svg>
</button>
<a href="/docs/" class="header-logo"><div class="header-logo-mark">A</div>AAMOS</a>
<span class="header-badge">API DOCS</span>
<nav class="header-nav">
<a href="https://aamos.ai">Home</a>
<a href="/docs/">Docs</a>
<a href="https://aamos.ai/signup/" class="btn-primary">Get API Key</a>
</nav>
</header>
<div class="overlay" id="overlay"></div>
<div class="layout">
<nav class="sidebar" id="sidebar">
<div class="sidebar-section">
<div class="sidebar-label">Getting Started</div>
<a href="/docs/" class="sidebar-link">
<svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M2 2h12v2H2zm0 4h12v2H2zm0 4h8v2H2z"/></svg>Overview</a>
<a href="/docs/authentication/" class="sidebar-link active">
<svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1a4 4 0 110 8A4 4 0 018 1zm0 10c-4 0-7 2-7 3v1h14v-1c0-1-3-3-7-3z"/></svg>Authentication</a>
</div>
<div class="sidebar-section">
<div class="sidebar-label">APIs</div>
<a href="/docs/vims/" class="sidebar-link"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M1 3h14v2H1zm2 4h10v2H3zm2 4h6v2H5z"/></svg>VIMS</a>
<a href="/docs/reality-alerts/" class="sidebar-link"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1L1 14h14L8 1zm0 3l5 9H3l5-9zm-1 3v3h2V7H7zm0 4v2h2v-2H7z"/></svg>Reality Alerts</a>
<a href="/docs/kyz/" class="sidebar-link"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1a4 4 0 110 8A4 4 0 018 1zm0 10c-4 0-7 2-7 3v1h14v-1c0-1-3-3-7-3z"/></svg>KYZ</a>
<a href="/docs/modules/" class="sidebar-link"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M2 2h5v5H2zm7 0h5v5H9zm-7 7h5v5H2zm7 0h5v5H9z"/></svg>AAMOS Modules</a>
</div>
<div class="sidebar-section">
<div class="sidebar-label">Resources</div>
<a href="https://aamos.ai" class="sidebar-link"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0a8 8 0 100 16A8 8 0 008 0zm0 2a6 6 0 010 12A6 6 0 018 2zm0 2a4 4 0 100 8 4 4 0 000-8z"/></svg>Website</a>
<a href="https://aamos.ai/signup/" class="sidebar-link"><svg class="icon" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1a4 4 0 110 8A4 4 0 018 1zm5 11H3a1 1 0 00-1 1v2h14v-2a1 1 0 00-1-1z"/></svg>Sign Up</a>
</div>
</nav>
<main class="main">
<div class="page-header">
<div class="breadcrumb"><a href="/docs/">Docs</a> / Authentication</div>
<h1>Authentication</h1>
<p class="page-desc">How to authenticate requests to the AAMOS API using API keys.</p>
</div>
<h2>Getting an API Key</h2>
<p>To access the AAMOS API, you need an API key. Sign up for a free account at <a href="https://aamos.ai/signup/">aamos.ai/signup</a> and generate a key from your dashboard under <strong>Settings → API Keys</strong>.</p>
<div class="info-box">
<span class="info-box-icon">🔑</span>
<div class="info-box-body"><strong>Keep your key secret.</strong> Your API key grants full access to your account. Never include it in client-side JavaScript, public repositories, or log files.</div>
</div>
<h2>Request Format</h2>
<p>Include your API key in every request as a Bearer token in the <code class="inline-code">Authorization</code> header:</p>
<div class="code-wrap">
<span class="code-lang">http</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
<pre>Authorization: Bearer ak_live_xxxxxxxxxxxxxxxxxxxx</pre>
</div>
<p>Here's a complete example with curl:</p>
<div class="code-wrap">
<span class="code-lang">bash</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
<pre><span class="tok-kw">curl</span> <span class="tok-str">https://api.aamos.ai/v1/vims/objects</span> \
-H <span class="tok-str">"Authorization: Bearer ak_live_YOUR_KEY"</span> \
-H <span class="tok-str">"Content-Type: application/json"</span></pre>
</div>
<h3>API Key format</h3>
<p>Live keys follow the format:</p>
<div class="code-wrap">
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
<pre>ak_live_[32-character alphanumeric string]</pre>
</div>
<p>Test keys (for sandbox) use:</p>
<div class="code-wrap">
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
<pre>ak_test_[32-character alphanumeric string]</pre>
</div>
<hr class="divider">
<h2>Rate Limits</h2>
<p>Rate limits are enforced per API key, per minute. Limits vary by plan:</p>
<table class="tier-table">
<thead>
<tr>
<th>Plan</th>
<th>Requests / minute</th>
<th>Requests / day</th>
<th>File uploads / day</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="tier-badge tier-free">Free</span></td>
<td>20</td>
<td>1,000</td>
<td>100</td>
</tr>
<tr>
<td><span class="tier-badge tier-starter">Starter</span></td>
<td>100</td>
<td>10,000</td>
<td>1,000</td>
</tr>
<tr>
<td><span class="tier-badge tier-pro">Pro</span></td>
<td>500</td>
<td>100,000</td>
<td>Unlimited</td>
</tr>
</tbody>
</table>
<p>Rate limit headers are included in every response:</p>
<div class="code-wrap">
<span class="code-lang">http</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
<pre>X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1705312800</pre>
</div>
<hr class="divider">
<h2>Error Responses</h2>
<p>Authentication and authorization errors return appropriate HTTP status codes with a JSON body:</p>
<table class="error-table">
<thead>
<tr>
<th>Status</th>
<th>Code</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="status-badge s-401">401</span></td>
<td><code class="inline-code">unauthorized</code></td>
<td>Missing or invalid API key. Check your Authorization header.</td>
</tr>
<tr>
<td><span class="status-badge s-403">403</span></td>
<td><code class="inline-code">forbidden</code></td>
<td>Your key doesn't have permission for this resource or endpoint.</td>
</tr>
<tr>
<td><span class="status-badge s-429">429</span></td>
<td><code class="inline-code">rate_limit_exceeded</code></td>
<td>You've exceeded your plan's rate limit. Wait and retry, or upgrade your plan.</td>
</tr>
<tr>
<td><span class="status-badge s-500">500</span></td>
<td><code class="inline-code">internal_error</code></td>
<td>Something went wrong on our side. Retry with exponential backoff.</td>
</tr>
</tbody>
</table>
<p>Error response body format:</p>
<div class="code-wrap">
<span class="code-lang">json</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
<pre>{
<span class="tok-key">"error"</span>: {
<span class="tok-key">"code"</span>: <span class="tok-str">"unauthorized"</span>,
<span class="tok-key">"message"</span>: <span class="tok-str">"Invalid API key provided"</span>,
<span class="tok-key">"request_id"</span>: <span class="tok-str">"req_a1b2c3d4e5"</span>
}
}</pre>
</div>
<div class="info-box warning">
<span class="info-box-icon">⚠️</span>
<div class="info-box-body"><strong>Rate limit 429 handling:</strong> When you receive a 429, check the <code class="inline-code">Retry-After</code> header for the number of seconds to wait before retrying.</div>
</div>
<hr class="divider">
<h2>Complete Example</h2>
<p>A fully authenticated request to list VIMS objects:</p>
<div class="code-wrap">
<span class="code-lang">bash</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
<pre><span class="tok-kw">curl</span> -s <span class="tok-str">https://api.aamos.ai/v1/vims/objects</span> \
-H <span class="tok-str">"Authorization: Bearer ak_live_YOUR_KEY"</span> \
-H <span class="tok-str">"Accept: application/json"</span> | jq .</pre>
</div>
</main>
</div>
<script>
function copyCode(btn) {
const pre = btn.closest('.code-wrap').querySelector('pre');
navigator.clipboard.writeText(pre.innerText || pre.textContent).then(() => {
btn.textContent = 'Copied!'; btn.classList.add('copied');
setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 2000);
});
}
const hamburger = document.getElementById('hamburger');
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('overlay');
hamburger.addEventListener('click', () => { sidebar.classList.toggle('open'); overlay.classList.toggle('visible'); });
overlay.addEventListener('click', () => { sidebar.classList.remove('open'); overlay.classList.remove('visible'); });
</script>
</body>
</html>