fix: replace CDN Alpine with Flux directives, rebrand API portal
Some checks failed
CI / PHP 8.3 (push) Failing after 2s
CI / PHP 8.4 (push) Failing after 2s

- Remove CDN Alpine.js from layout (conflicted with Flux's bundled Alpine)
- Add @fluxAppearance and @fluxScripts for proper Flux UI rendering
- Replace all hardcoded "Host UK" with config('core.app.name')
- Migrate colour scheme from slate to zinc, blue to cyan (match MCP portal)
- Rewrite index/reference/quickstart with brain/score API endpoints
- Fix broken route('api.guides.biolinks') references causing 500 errors
- Replace api.host.uk.com URLs with api.lthn.ai

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-03-03 20:50:44 +00:00
parent e709545db1
commit 774ecae403
14 changed files with 592 additions and 739 deletions

View file

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Host UK API Documentation</title> <title>{{ config('core.app.name', 'Core PHP') }} API Documentation</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css"> <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
<style> <style>
html { html {
@ -72,10 +72,10 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75 22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3-4.5 16.5" /> <path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75 22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3-4.5 16.5" />
</svg> </svg>
Host UK API {{ config('core.app.name', 'Core PHP') }} API
</h1> </h1>
<nav class="api-header-links"> <nav class="api-header-links">
<a href="{{ config('app.url') }}">Host UK</a> <a href="{{ config('app.url') }}">{{ config('core.app.name', 'Core PHP') }}</a>
<a href="/openapi.json" target="_blank">OpenAPI JSON</a> <a href="/openapi.json" target="_blank">OpenAPI JSON</a>
<a href="{{ str_replace('api.', 'mcp.', request()->getSchemeAndHttpHost()) }}">MCP Portal</a> <a href="{{ str_replace('api.', 'mcp.', request()->getSchemeAndHttpHost()) }}">MCP Portal</a>
</nav> </nav>

View file

@ -6,32 +6,32 @@
<div class="flex"> <div class="flex">
{{-- Sidebar --}} {{-- Sidebar --}}
<aside class="hidden lg:block fixed left-0 top-16 md:top-20 bottom-0 w-64 border-r border-slate-200 dark:border-slate-800"> <aside class="hidden lg:block fixed left-0 top-16 md:top-20 bottom-0 w-64 border-r border-zinc-200 dark:border-zinc-800">
<div class="h-full px-4 py-8 overflow-y-auto no-scrollbar"> <div class="h-full px-4 py-8 overflow-y-auto no-scrollbar">
<nav> <nav>
<ul class="space-y-2"> <ul class="space-y-2">
<li> <li>
<a href="#overview" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#overview" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Overview Overview
</a> </a>
</li> </li>
<li> <li>
<a href="#api-keys" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#api-keys" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
API Keys API Keys
</a> </a>
</li> </li>
<li> <li>
<a href="#using-keys" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#using-keys" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Using API Keys Using API Keys
</a> </a>
</li> </li>
<li> <li>
<a href="#scopes" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#scopes" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Scopes Scopes
</a> </a>
</li> </li>
<li> <li>
<a href="#security" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#security" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Security Best Practices Security Best Practices
</a> </a>
</li> </li>
@ -47,35 +47,35 @@
{{-- Breadcrumb --}} {{-- Breadcrumb --}}
<nav class="mb-8"> <nav class="mb-8">
<ol class="flex items-center gap-2 text-sm"> <ol class="flex items-center gap-2 text-sm">
<li><a href="{{ route('api.guides') }}" class="text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200">Guides</a></li> <li><a href="{{ route('api.guides') }}" class="text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200">Guides</a></li>
<li class="text-slate-400">/</li> <li class="text-zinc-400">/</li>
<li class="text-slate-800 dark:text-slate-200">Authentication</li> <li class="text-zinc-800 dark:text-zinc-200">Authentication</li>
</ol> </ol>
</nav> </nav>
<h1 class="h1 mb-4 text-slate-800 dark:text-slate-100">Authentication</h1> <h1 class="h1 mb-4 text-zinc-800 dark:text-zinc-100">Authentication</h1>
<p class="text-xl text-slate-600 dark:text-slate-400 mb-12"> <p class="text-xl text-zinc-600 dark:text-zinc-400 mb-12">
Learn how to authenticate your API requests using API keys. Learn how to authenticate your API requests using API keys.
</p> </p>
{{-- Overview --}} {{-- Overview --}}
<section id="overview" data-scrollspy-target class="mb-12"> <section id="overview" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Overview</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Overview</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
The API uses API keys for authentication. Each API key is scoped to a specific workspace and has configurable permissions. The API uses API keys for authentication. Each API key is scoped to a specific workspace and has configurable permissions.
</p> </p>
<p class="text-slate-600 dark:text-slate-400"> <p class="text-zinc-600 dark:text-zinc-400">
API keys are prefixed with <code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-sm">hk_</code> to make them easily identifiable. API keys are prefixed with <code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-sm">hk_</code> to make them easily identifiable.
</p> </p>
</section> </section>
{{-- API Keys --}} {{-- API Keys --}}
<section id="api-keys" data-scrollspy-target class="mb-12"> <section id="api-keys" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">API Keys</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">API Keys</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
To create an API key: To create an API key:
</p> </p>
<ol class="list-decimal list-inside space-y-2 text-slate-600 dark:text-slate-400 mb-6"> <ol class="list-decimal list-inside space-y-2 text-zinc-600 dark:text-zinc-400 mb-6">
<li>Log in to your account</li> <li>Log in to your account</li>
<li>Navigate to <strong>Settings API Keys</strong></li> <li>Navigate to <strong>Settings API Keys</strong></li>
<li>Click <strong>Create API Key</strong></li> <li>Click <strong>Create API Key</strong></li>
@ -98,59 +98,59 @@
{{-- Using Keys --}} {{-- Using Keys --}}
<section id="using-keys" data-scrollspy-target class="mb-12"> <section id="using-keys" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Using API Keys</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Using API Keys</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Include your API key in the <code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-sm">Authorization</code> header as a Bearer token: Include your API key in the <code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-sm">Authorization</code> header as a Bearer token:
</p> </p>
<div class="bg-slate-800 rounded-sm overflow-hidden mb-6"> <div class="bg-zinc-800 rounded-sm overflow-hidden mb-6">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700"> <div class="flex items-center justify-between px-4 py-2 border-b border-zinc-700">
<span class="text-sm text-slate-400">HTTP Header</span> <span class="text-sm text-zinc-400">HTTP Header</span>
</div> </div>
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300">Authorization: Bearer hk_your_api_key_here</code></pre> <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300">Authorization: Bearer hk_your_api_key_here</code></pre>
</div> </div>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Example request with cURL: Example request with cURL:
</p> </p>
<div class="bg-slate-800 rounded-sm overflow-hidden"> <div class="bg-zinc-800 rounded-sm overflow-hidden">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700"> <div class="flex items-center justify-between px-4 py-2 border-b border-zinc-700">
<span class="text-sm text-slate-400">cURL</span> <span class="text-sm text-zinc-400">cURL</span>
</div> </div>
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300"><span class="text-teal-400">curl</span> <span class="text-slate-500">--request</span> GET \ <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300"><span class="text-teal-400">curl</span> <span class="text-zinc-500">--request</span> GET \
<span class="text-slate-500">--url</span> <span class="text-amber-400">'https://api.host.uk.com/api/v1/bio'</span> \ <span class="text-zinc-500">--url</span> <span class="text-amber-400">'https://api.lthn.ai/v1/brain/recall'</span> \
<span class="text-slate-500">--header</span> <span class="text-amber-400">'Authorization: Bearer hk_your_api_key'</span></code></pre> <span class="text-zinc-500">--header</span> <span class="text-amber-400">'Authorization: Bearer hk_your_api_key'</span></code></pre>
</div> </div>
</section> </section>
{{-- Scopes --}} {{-- Scopes --}}
<section id="scopes" data-scrollspy-target class="mb-12"> <section id="scopes" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Scopes</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Scopes</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
API keys can have different scopes to limit their permissions: API keys can have different scopes to limit their permissions:
</p> </p>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="w-full text-sm"> <table class="w-full text-sm">
<thead> <thead>
<tr class="border-b border-slate-200 dark:border-slate-700"> <tr class="border-b border-zinc-200 dark:border-zinc-700">
<th class="text-left py-3 px-4 font-medium text-slate-800 dark:text-slate-200">Scope</th> <th class="text-left py-3 px-4 font-medium text-zinc-800 dark:text-zinc-200">Scope</th>
<th class="text-left py-3 px-4 font-medium text-slate-800 dark:text-slate-200">Description</th> <th class="text-left py-3 px-4 font-medium text-zinc-800 dark:text-zinc-200">Description</th>
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-slate-200 dark:divide-slate-700"> <tbody class="divide-y divide-zinc-200 dark:divide-zinc-700">
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">read</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">read</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Read access to resources (GET requests)</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Read access to resources (GET requests)</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">write</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">write</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Create and update resources (POST, PUT requests)</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Create and update resources (POST, PUT requests)</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">delete</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">delete</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Delete resources (DELETE requests)</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Delete resources (DELETE requests)</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -159,8 +159,8 @@
{{-- Security --}} {{-- Security --}}
<section id="security" data-scrollspy-target class="mb-12"> <section id="security" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Security Best Practices</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Security Best Practices</h2>
<ul class="list-disc list-inside space-y-2 text-slate-600 dark:text-slate-400"> <ul class="list-disc list-inside space-y-2 text-zinc-600 dark:text-zinc-400">
<li>Never commit API keys to version control</li> <li>Never commit API keys to version control</li>
<li>Use environment variables to store keys</li> <li>Use environment variables to store keys</li>
<li>Rotate keys periodically</li> <li>Rotate keys periodically</li>
@ -171,12 +171,12 @@
</section> </section>
{{-- Next steps --}} {{-- Next steps --}}
<div class="flex items-center justify-between pt-8 border-t border-slate-200 dark:border-slate-700"> <div class="flex items-center justify-between pt-8 border-t border-zinc-200 dark:border-zinc-700">
<a href="{{ route('api.guides.quickstart') }}" class="text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200"> <a href="{{ route('api.guides.quickstart') }}" class="text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200">
&larr; Quick Start &larr; Quick Start
</a> </a>
<a href="{{ route('api.guides.biolinks') }}" class="text-blue-600 hover:text-blue-700 dark:hover:text-blue-500 font-medium"> <a href="{{ route('api.guides.webhooks') }}" class="text-cyan-600 hover:text-cyan-700 dark:hover:text-cyan-500 font-medium">
Managing Biolinks &rarr; Webhooks &rarr;
</a> </a>
</div> </div>

View file

@ -6,32 +6,32 @@
<div class="flex"> <div class="flex">
{{-- Sidebar --}} {{-- Sidebar --}}
<aside class="hidden lg:block fixed left-0 top-16 md:top-20 bottom-0 w-64 border-r border-slate-200 dark:border-slate-800"> <aside class="hidden lg:block fixed left-0 top-16 md:top-20 bottom-0 w-64 border-r border-zinc-200 dark:border-zinc-800">
<div class="h-full px-4 py-8 overflow-y-auto no-scrollbar"> <div class="h-full px-4 py-8 overflow-y-auto no-scrollbar">
<nav> <nav>
<ul class="space-y-2"> <ul class="space-y-2">
<li> <li>
<a href="#overview" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#overview" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Overview Overview
</a> </a>
</li> </li>
<li> <li>
<a href="#http-codes" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#http-codes" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
HTTP Status Codes HTTP Status Codes
</a> </a>
</li> </li>
<li> <li>
<a href="#error-format" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#error-format" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Error Format Error Format
</a> </a>
</li> </li>
<li> <li>
<a href="#common-errors" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#common-errors" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Common Errors Common Errors
</a> </a>
</li> </li>
<li> <li>
<a href="#rate-limiting" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#rate-limiting" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Rate Limiting Rate Limiting
</a> </a>
</li> </li>
@ -47,73 +47,73 @@
{{-- Breadcrumb --}} {{-- Breadcrumb --}}
<nav class="mb-8"> <nav class="mb-8">
<ol class="flex items-center gap-2 text-sm"> <ol class="flex items-center gap-2 text-sm">
<li><a href="{{ route('api.guides') }}" class="text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200">Guides</a></li> <li><a href="{{ route('api.guides') }}" class="text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200">Guides</a></li>
<li class="text-slate-400">/</li> <li class="text-zinc-400">/</li>
<li class="text-slate-800 dark:text-slate-200">Error Handling</li> <li class="text-zinc-800 dark:text-zinc-200">Error Handling</li>
</ol> </ol>
</nav> </nav>
<h1 class="h1 mb-4 text-slate-800 dark:text-slate-100">Error Handling</h1> <h1 class="h1 mb-4 text-zinc-800 dark:text-zinc-100">Error Handling</h1>
<p class="text-xl text-slate-600 dark:text-slate-400 mb-12"> <p class="text-xl text-zinc-600 dark:text-zinc-400 mb-12">
Understand API error codes and how to handle them gracefully. Understand API error codes and how to handle them gracefully.
</p> </p>
{{-- Overview --}} {{-- Overview --}}
<section id="overview" data-scrollspy-target class="mb-12"> <section id="overview" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Overview</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Overview</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
The API uses conventional HTTP response codes to indicate success or failure. Codes in the 2xx range indicate success, 4xx indicate client errors, and 5xx indicate server errors. The API uses conventional HTTP response codes to indicate success or failure. Codes in the 2xx range indicate success, 4xx indicate client errors, and 5xx indicate server errors.
</p> </p>
</section> </section>
{{-- HTTP Codes --}} {{-- HTTP Codes --}}
<section id="http-codes" data-scrollspy-target class="mb-12"> <section id="http-codes" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">HTTP Status Codes</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">HTTP Status Codes</h2>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="w-full text-sm"> <table class="w-full text-sm">
<thead> <thead>
<tr class="border-b border-slate-200 dark:border-slate-700"> <tr class="border-b border-zinc-200 dark:border-zinc-700">
<th class="text-left py-3 px-4 font-medium text-slate-800 dark:text-slate-200">Code</th> <th class="text-left py-3 px-4 font-medium text-zinc-800 dark:text-zinc-200">Code</th>
<th class="text-left py-3 px-4 font-medium text-slate-800 dark:text-slate-200">Meaning</th> <th class="text-left py-3 px-4 font-medium text-zinc-800 dark:text-zinc-200">Meaning</th>
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-slate-200 dark:divide-slate-700"> <tbody class="divide-y divide-zinc-200 dark:divide-zinc-700">
<tr> <tr>
<td class="py-3 px-4"><span class="px-2 py-1 bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 rounded text-xs font-medium">200</span></td> <td class="py-3 px-4"><span class="px-2 py-1 bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 rounded text-xs font-medium">200</span></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Success - Request completed successfully</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Success - Request completed successfully</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><span class="px-2 py-1 bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 rounded text-xs font-medium">201</span></td> <td class="py-3 px-4"><span class="px-2 py-1 bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 rounded text-xs font-medium">201</span></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Created - Resource was created successfully</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Created - Resource was created successfully</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><span class="px-2 py-1 bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 rounded text-xs font-medium">400</span></td> <td class="py-3 px-4"><span class="px-2 py-1 bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 rounded text-xs font-medium">400</span></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Bad Request - Invalid request parameters</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Bad Request - Invalid request parameters</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><span class="px-2 py-1 bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 rounded text-xs font-medium">401</span></td> <td class="py-3 px-4"><span class="px-2 py-1 bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 rounded text-xs font-medium">401</span></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Unauthorised - Invalid or missing API key</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Unauthorised - Invalid or missing API key</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><span class="px-2 py-1 bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 rounded text-xs font-medium">403</span></td> <td class="py-3 px-4"><span class="px-2 py-1 bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 rounded text-xs font-medium">403</span></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Forbidden - Insufficient permissions</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Forbidden - Insufficient permissions</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><span class="px-2 py-1 bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 rounded text-xs font-medium">404</span></td> <td class="py-3 px-4"><span class="px-2 py-1 bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 rounded text-xs font-medium">404</span></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Not Found - Resource doesn't exist</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Not Found - Resource doesn't exist</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><span class="px-2 py-1 bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 rounded text-xs font-medium">422</span></td> <td class="py-3 px-4"><span class="px-2 py-1 bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 rounded text-xs font-medium">422</span></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Unprocessable - Validation failed</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Unprocessable - Validation failed</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><span class="px-2 py-1 bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 rounded text-xs font-medium">429</span></td> <td class="py-3 px-4"><span class="px-2 py-1 bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 rounded text-xs font-medium">429</span></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Too Many Requests - Rate limit exceeded</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Too Many Requests - Rate limit exceeded</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><span class="px-2 py-1 bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400 rounded text-xs font-medium">500</span></td> <td class="py-3 px-4"><span class="px-2 py-1 bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400 rounded text-xs font-medium">500</span></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Server Error - Something went wrong on our end</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Server Error - Something went wrong on our end</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -122,13 +122,13 @@
{{-- Error Format --}} {{-- Error Format --}}
<section id="error-format" data-scrollspy-target class="mb-12"> <section id="error-format" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Error Format</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Error Format</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Error responses include a JSON body with details: Error responses include a JSON body with details:
</p> </p>
<div class="bg-slate-800 rounded-sm overflow-hidden"> <div class="bg-zinc-800 rounded-sm overflow-hidden">
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300">{ <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300">{
<span class="text-blue-400">"message"</span>: <span class="text-green-400">"The given data was invalid."</span>, <span class="text-blue-400">"message"</span>: <span class="text-green-400">"The given data was invalid."</span>,
<span class="text-blue-400">"errors"</span>: { <span class="text-blue-400">"errors"</span>: {
<span class="text-blue-400">"url"</span>: [ <span class="text-blue-400">"url"</span>: [
@ -141,65 +141,65 @@
{{-- Common Errors --}} {{-- Common Errors --}}
<section id="common-errors" data-scrollspy-target class="mb-12"> <section id="common-errors" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Common Errors</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Common Errors</h2>
<div class="space-y-4"> <div class="space-y-4">
<div class="p-4 border border-slate-200 dark:border-slate-700 rounded-sm"> <div class="p-4 border border-zinc-200 dark:border-zinc-700 rounded-sm">
<h4 class="font-medium text-slate-800 dark:text-slate-200 mb-2">Invalid API Key</h4> <h4 class="font-medium text-zinc-800 dark:text-zinc-200 mb-2">Invalid API Key</h4>
<p class="text-sm text-slate-600 dark:text-slate-400 mb-2"> <p class="text-sm text-zinc-600 dark:text-zinc-400 mb-2">
Returned when the API key is missing, malformed, or revoked. Returned when the API key is missing, malformed, or revoked.
</p> </p>
<code class="text-xs px-2 py-1 bg-slate-100 dark:bg-slate-800 rounded">401 Unauthorised</code> <code class="text-xs px-2 py-1 bg-zinc-100 dark:bg-zinc-800 rounded">401 Unauthorised</code>
</div> </div>
<div class="p-4 border border-slate-200 dark:border-slate-700 rounded-sm"> <div class="p-4 border border-zinc-200 dark:border-zinc-700 rounded-sm">
<h4 class="font-medium text-slate-800 dark:text-slate-200 mb-2">Resource Not Found</h4> <h4 class="font-medium text-zinc-800 dark:text-zinc-200 mb-2">Resource Not Found</h4>
<p class="text-sm text-slate-600 dark:text-slate-400 mb-2"> <p class="text-sm text-zinc-600 dark:text-zinc-400 mb-2">
The requested resource (biolink, workspace, etc.) doesn't exist or you don't have access. The requested resource (biolink, workspace, etc.) doesn't exist or you don't have access.
</p> </p>
<code class="text-xs px-2 py-1 bg-slate-100 dark:bg-slate-800 rounded">404 Not Found</code> <code class="text-xs px-2 py-1 bg-zinc-100 dark:bg-zinc-800 rounded">404 Not Found</code>
</div> </div>
<div class="p-4 border border-slate-200 dark:border-slate-700 rounded-sm"> <div class="p-4 border border-zinc-200 dark:border-zinc-700 rounded-sm">
<h4 class="font-medium text-slate-800 dark:text-slate-200 mb-2">Validation Failed</h4> <h4 class="font-medium text-zinc-800 dark:text-zinc-200 mb-2">Validation Failed</h4>
<p class="text-sm text-slate-600 dark:text-slate-400 mb-2"> <p class="text-sm text-zinc-600 dark:text-zinc-400 mb-2">
Request data failed validation. Check the <code>errors</code> object for specific fields. Request data failed validation. Check the <code>errors</code> object for specific fields.
</p> </p>
<code class="text-xs px-2 py-1 bg-slate-100 dark:bg-slate-800 rounded">422 Unprocessable Entity</code> <code class="text-xs px-2 py-1 bg-zinc-100 dark:bg-zinc-800 rounded">422 Unprocessable Entity</code>
</div> </div>
</div> </div>
</section> </section>
{{-- Rate Limiting --}} {{-- Rate Limiting --}}
<section id="rate-limiting" data-scrollspy-target class="mb-12"> <section id="rate-limiting" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Rate Limiting</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Rate Limiting</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
API requests are rate limited to ensure fair usage. Rate limit headers are included in all responses: API requests are rate limited to ensure fair usage. Rate limit headers are included in all responses:
</p> </p>
<div class="bg-slate-800 rounded-sm overflow-hidden mb-4"> <div class="bg-zinc-800 rounded-sm overflow-hidden mb-4">
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300">X-RateLimit-Limit: 60 <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300">X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58 X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1705320000</code></pre> X-RateLimit-Reset: 1705320000</code></pre>
</div> </div>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
When rate limited, you'll receive a 429 response. Wait until the reset timestamp before retrying. When rate limited, you'll receive a 429 response. Wait until the reset timestamp before retrying.
</p> </p>
<div class="text-sm p-4 bg-slate-50 border border-slate-200 rounded-sm dark:bg-slate-800 dark:border-slate-700"> <div class="text-sm p-4 bg-zinc-50 border border-zinc-200 rounded-sm dark:bg-zinc-800 dark:border-zinc-700">
<p class="text-slate-600 dark:text-slate-400"> <p class="text-zinc-600 dark:text-zinc-400">
<strong>Tip:</strong> Implement exponential backoff in your retry logic. Start with a 1-second delay and double it with each retry, up to a maximum of 32 seconds. <strong>Tip:</strong> Implement exponential backoff in your retry logic. Start with a 1-second delay and double it with each retry, up to a maximum of 32 seconds.
</p> </p>
</div> </div>
</section> </section>
{{-- Next steps --}} {{-- Next steps --}}
<div class="flex items-center justify-between pt-8 border-t border-slate-200 dark:border-slate-700"> <div class="flex items-center justify-between pt-8 border-t border-zinc-200 dark:border-zinc-700">
<a href="{{ route('api.guides.webhooks') }}" class="text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200"> <a href="{{ route('api.guides.webhooks') }}" class="text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200">
&larr; Webhooks &larr; Webhooks
</a> </a>
<a href="{{ route('api.reference') }}" class="text-blue-600 hover:text-blue-700 dark:hover:text-blue-500 font-medium"> <a href="{{ route('api.reference') }}" class="text-cyan-600 hover:text-cyan-700 dark:hover:text-cyan-500 font-medium">
API Reference &rarr; API Reference &rarr;
</a> </a>
</div> </div>

View file

@ -5,8 +5,8 @@
@section('content') @section('content')
<div class="max-w-7xl mx-auto px-4 sm:px-6 py-12"> <div class="max-w-7xl mx-auto px-4 sm:px-6 py-12">
<div class="max-w-3xl"> <div class="max-w-3xl">
<h1 class="h2 mb-4 text-slate-800 dark:text-slate-100">Guides</h1> <h1 class="h2 mb-4 text-zinc-800 dark:text-zinc-100">Guides</h1>
<p class="text-lg text-slate-600 dark:text-slate-400 mb-12"> <p class="text-lg text-zinc-600 dark:text-zinc-400 mb-12">
Step-by-step tutorials and best practices for integrating with the API. Step-by-step tutorials and best practices for integrating with the API.
</p> </p>
</div> </div>
@ -14,73 +14,51 @@
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{{-- Quick Start --}} {{-- Quick Start --}}
<a href="{{ route('api.guides.quickstart') }}" class="group block p-6 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-sm hover:border-blue-300 dark:hover:border-blue-600 transition-colors"> <a href="{{ route('api.guides.quickstart') }}" class="group block p-6 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-sm hover:border-cyan-300 dark:hover:border-cyan-600 transition-colors">
<div class="flex items-center gap-3 mb-3"> <div class="flex items-center gap-3 mb-3">
<div class="w-8 h-8 flex items-center justify-center bg-blue-100 dark:bg-blue-900/30 rounded-sm"> <div class="w-8 h-8 flex items-center justify-center bg-cyan-100 dark:bg-cyan-900/30 rounded-sm">
<svg class="w-4 h-4 fill-blue-600" viewBox="0 0 16 16"> <i class="fa-solid fa-bolt text-cyan-600 text-sm"></i>
<path d="M11.953 4.29a.5.5 0 0 0-.454-.292H6.14L6.984.62A.5.5 0 0 0 6.12.173l-6 7a.5.5 0 0 0 .379.825h5.359l-.844 3.38a.5.5 0 0 0 .864.445l6-7a.5.5 0 0 0 .075-.534Z" />
</svg>
</div> </div>
<span class="text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Getting Started</span> <span class="text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">Getting Started</span>
</div> </div>
<h3 class="h4 mb-2 text-slate-800 dark:text-slate-100 group-hover:text-blue-600 dark:group-hover:text-blue-500">Quick Start</h3> <h3 class="h4 mb-2 text-zinc-800 dark:text-zinc-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-500">Quick Start</h3>
<p class="text-sm text-slate-600 dark:text-slate-400">Get up and running with the API in under 5 minutes.</p> <p class="text-sm text-zinc-600 dark:text-zinc-400">Get up and running with the API in under 5 minutes.</p>
</a> </a>
{{-- Authentication --}} {{-- Authentication --}}
<a href="{{ route('api.guides.authentication') }}" class="group block p-6 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-sm hover:border-blue-300 dark:hover:border-blue-600 transition-colors"> <a href="{{ route('api.guides.authentication') }}" class="group block p-6 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-sm hover:border-cyan-300 dark:hover:border-cyan-600 transition-colors">
<div class="flex items-center gap-3 mb-3"> <div class="flex items-center gap-3 mb-3">
<div class="w-8 h-8 flex items-center justify-center bg-amber-100 dark:bg-amber-900/30 rounded-sm"> <div class="w-8 h-8 flex items-center justify-center bg-amber-100 dark:bg-amber-900/30 rounded-sm">
<svg class="w-4 h-4 fill-amber-600" viewBox="0 0 16 16"> <i class="fa-solid fa-key text-amber-600 text-sm"></i>
<path d="M8 1a4 4 0 0 0-4 4v3H3a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1h-1V5a4 4 0 0 0-4-4zm2 7V5a2 2 0 1 0-4 0v3h4z"/>
</svg>
</div> </div>
<span class="text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Security</span> <span class="text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">Security</span>
</div> </div>
<h3 class="h4 mb-2 text-slate-800 dark:text-slate-100 group-hover:text-blue-600 dark:group-hover:text-blue-500">Authentication</h3> <h3 class="h4 mb-2 text-zinc-800 dark:text-zinc-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-500">Authentication</h3>
<p class="text-sm text-slate-600 dark:text-slate-400">Learn how to authenticate your API requests using API keys.</p> <p class="text-sm text-zinc-600 dark:text-zinc-400">Learn how to authenticate your API requests using API keys.</p>
</a>
{{-- QR Codes --}}
<a href="{{ route('api.guides.qrcodes') }}" class="group block p-6 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-sm hover:border-blue-300 dark:hover:border-blue-600 transition-colors">
<div class="flex items-center gap-3 mb-3">
<div class="w-8 h-8 flex items-center justify-center bg-teal-100 dark:bg-teal-900/30 rounded-sm">
<svg class="w-4 h-4 fill-teal-600" viewBox="0 0 16 16">
<path d="M2 3a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3zm2 2V4h1v1H4z"/>
</svg>
</div>
<span class="text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Core</span>
</div>
<h3 class="h4 mb-2 text-slate-800 dark:text-slate-100 group-hover:text-blue-600 dark:group-hover:text-blue-500">QR Code Generation</h3>
<p class="text-sm text-slate-600 dark:text-slate-400">Generate customisable QR codes with colours, logos, and formats.</p>
</a> </a>
{{-- Webhooks --}} {{-- Webhooks --}}
<a href="{{ route('api.guides.webhooks') }}" class="group block p-6 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-sm hover:border-blue-300 dark:hover:border-blue-600 transition-colors"> <a href="{{ route('api.guides.webhooks') }}" class="group block p-6 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-sm hover:border-cyan-300 dark:hover:border-cyan-600 transition-colors">
<div class="flex items-center gap-3 mb-3"> <div class="flex items-center gap-3 mb-3">
<div class="w-8 h-8 flex items-center justify-center bg-rose-100 dark:bg-rose-900/30 rounded-sm"> <div class="w-8 h-8 flex items-center justify-center bg-rose-100 dark:bg-rose-900/30 rounded-sm">
<svg class="w-4 h-4 fill-rose-600" viewBox="0 0 16 16"> <i class="fa-solid fa-satellite-dish text-rose-600 text-sm"></i>
<path d="M8.94 1.5a.75.75 0 0 0-1.06 0L7 2.38 6.12 1.5a.75.75 0 0 0-1.06 1.06l.88.88-.88.88a.75.75 0 1 0 1.06 1.06L7 4.5l.88.88a.75.75 0 1 0 1.06-1.06l-.88-.88.88-.88a.75.75 0 0 0 0-1.06z"/>
</svg>
</div> </div>
<span class="text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Advanced</span> <span class="text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">Advanced</span>
</div> </div>
<h3 class="h4 mb-2 text-slate-800 dark:text-slate-100 group-hover:text-blue-600 dark:group-hover:text-blue-500">Webhooks</h3> <h3 class="h4 mb-2 text-zinc-800 dark:text-zinc-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-500">Webhooks</h3>
<p class="text-sm text-slate-600 dark:text-slate-400">Receive real-time notifications for events in your workspace.</p> <p class="text-sm text-zinc-600 dark:text-zinc-400">Receive real-time notifications for events in your workspace.</p>
</a> </a>
{{-- Error Handling --}} {{-- Error Handling --}}
<a href="{{ route('api.guides.errors') }}" class="group block p-6 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-sm hover:border-blue-300 dark:hover:border-blue-600 transition-colors"> <a href="{{ route('api.guides.errors') }}" class="group block p-6 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-sm hover:border-cyan-300 dark:hover:border-cyan-600 transition-colors">
<div class="flex items-center gap-3 mb-3"> <div class="flex items-center gap-3 mb-3">
<div class="w-8 h-8 flex items-center justify-center bg-slate-100 dark:bg-slate-700 rounded-sm"> <div class="w-8 h-8 flex items-center justify-center bg-zinc-100 dark:bg-zinc-700 rounded-sm">
<svg class="w-4 h-4 fill-slate-600 dark:fill-slate-400" viewBox="0 0 16 16"> <i class="fa-solid fa-triangle-exclamation text-zinc-600 dark:text-zinc-400 text-sm"></i>
<path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 3a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
</svg>
</div> </div>
<span class="text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">Reference</span> <span class="text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">Reference</span>
</div> </div>
<h3 class="h4 mb-2 text-slate-800 dark:text-slate-100 group-hover:text-blue-600 dark:group-hover:text-blue-500">Error Handling</h3> <h3 class="h4 mb-2 text-zinc-800 dark:text-zinc-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-500">Error Handling</h3>
<p class="text-sm text-slate-600 dark:text-slate-400">Understand API error codes and how to handle them gracefully.</p> <p class="text-sm text-zinc-600 dark:text-zinc-400">Understand API error codes and how to handle them gracefully.</p>
</a> </a>
</div> </div>

View file

@ -6,32 +6,32 @@
<div class="flex"> <div class="flex">
{{-- Sidebar --}} {{-- Sidebar --}}
<aside class="hidden lg:block fixed left-0 top-16 md:top-20 bottom-0 w-64 border-r border-slate-200 dark:border-slate-800"> <aside class="hidden lg:block fixed left-0 top-16 md:top-20 bottom-0 w-64 border-r border-zinc-200 dark:border-zinc-800">
<div class="h-full px-4 py-8 overflow-y-auto no-scrollbar"> <div class="h-full px-4 py-8 overflow-y-auto no-scrollbar">
<nav> <nav>
<ul class="space-y-2"> <ul class="space-y-2">
<li> <li>
<a href="#overview" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#overview" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Overview Overview
</a> </a>
</li> </li>
<li> <li>
<a href="#biolink-qr" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#biolink-qr" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Biolink QR Codes Biolink QR Codes
</a> </a>
</li> </li>
<li> <li>
<a href="#custom-qr" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#custom-qr" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Custom URL QR Codes Custom URL QR Codes
</a> </a>
</li> </li>
<li> <li>
<a href="#options" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#options" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Customisation Options Customisation Options
</a> </a>
</li> </li>
<li> <li>
<a href="#download" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#download" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Download Formats Download Formats
</a> </a>
</li> </li>
@ -47,24 +47,24 @@
{{-- Breadcrumb --}} {{-- Breadcrumb --}}
<nav class="mb-8"> <nav class="mb-8">
<ol class="flex items-center gap-2 text-sm"> <ol class="flex items-center gap-2 text-sm">
<li><a href="{{ route('api.guides') }}" class="text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200">Guides</a></li> <li><a href="{{ route('api.guides') }}" class="text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200">Guides</a></li>
<li class="text-slate-400">/</li> <li class="text-zinc-400">/</li>
<li class="text-slate-800 dark:text-slate-200">QR Code Generation</li> <li class="text-zinc-800 dark:text-zinc-200">QR Code Generation</li>
</ol> </ol>
</nav> </nav>
<h1 class="h1 mb-4 text-slate-800 dark:text-slate-100">QR Code Generation</h1> <h1 class="h1 mb-4 text-zinc-800 dark:text-zinc-100">QR Code Generation</h1>
<p class="text-xl text-slate-600 dark:text-slate-400 mb-12"> <p class="text-xl text-zinc-600 dark:text-zinc-400 mb-12">
Generate customisable QR codes for your biolinks or any URL. Generate customisable QR codes for your biolinks or any URL.
</p> </p>
{{-- Overview --}} {{-- Overview --}}
<section id="overview" data-scrollspy-target class="mb-12"> <section id="overview" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Overview</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Overview</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
The API provides two ways to generate QR codes: The API provides two ways to generate QR codes:
</p> </p>
<ul class="list-disc list-inside space-y-2 text-slate-600 dark:text-slate-400"> <ul class="list-disc list-inside space-y-2 text-zinc-600 dark:text-zinc-400">
<li><strong>Biolink QR codes</strong> - Generate QR codes for your existing biolinks</li> <li><strong>Biolink QR codes</strong> - Generate QR codes for your existing biolinks</li>
<li><strong>Custom URL QR codes</strong> - Generate QR codes for any URL</li> <li><strong>Custom URL QR codes</strong> - Generate QR codes for any URL</li>
</ul> </ul>
@ -72,24 +72,24 @@
{{-- Biolink QR --}} {{-- Biolink QR --}}
<section id="biolink-qr" data-scrollspy-target class="mb-12"> <section id="biolink-qr" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Biolink QR Codes</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Biolink QR Codes</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Get QR code data for an existing biolink: Get QR code data for an existing biolink:
</p> </p>
<div class="bg-slate-800 rounded-sm overflow-hidden mb-4"> <div class="bg-zinc-800 rounded-sm overflow-hidden mb-4">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700"> <div class="flex items-center justify-between px-4 py-2 border-b border-zinc-700">
<span class="text-sm text-slate-400">cURL</span> <span class="text-sm text-zinc-400">cURL</span>
</div> </div>
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300"><span class="text-teal-400">curl</span> <span class="text-slate-500">--request</span> GET \ <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300"><span class="text-teal-400">curl</span> <span class="text-zinc-500">--request</span> GET \
<span class="text-slate-500">--url</span> <span class="text-amber-400">'https://api.host.uk.com/api/v1/bio/1/qr'</span> \ <span class="text-zinc-500">--url</span> <span class="text-amber-400">'https://api.lthn.ai/api/v1/bio/1/qr'</span> \
<span class="text-slate-500">--header</span> <span class="text-amber-400">'Authorization: Bearer YOUR_API_KEY'</span></code></pre> <span class="text-zinc-500">--header</span> <span class="text-amber-400">'Authorization: Bearer YOUR_API_KEY'</span></code></pre>
</div> </div>
<p class="text-slate-600 dark:text-slate-400 mb-4">Response:</p> <p class="text-zinc-600 dark:text-zinc-400 mb-4">Response:</p>
<div class="bg-slate-800 rounded-sm overflow-hidden"> <div class="bg-zinc-800 rounded-sm overflow-hidden">
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300">{ <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300">{
<span class="text-blue-400">"data"</span>: { <span class="text-blue-400">"data"</span>: {
<span class="text-blue-400">"svg"</span>: <span class="text-green-400">"&lt;svg&gt;...&lt;/svg&gt;"</span>, <span class="text-blue-400">"svg"</span>: <span class="text-green-400">"&lt;svg&gt;...&lt;/svg&gt;"</span>,
<span class="text-blue-400">"url"</span>: <span class="text-green-400">"https://example.com/mypage"</span> <span class="text-blue-400">"url"</span>: <span class="text-green-400">"https://example.com/mypage"</span>
@ -100,20 +100,20 @@
{{-- Custom QR --}} {{-- Custom QR --}}
<section id="custom-qr" data-scrollspy-target class="mb-12"> <section id="custom-qr" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Custom URL QR Codes</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Custom URL QR Codes</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Generate a QR code for any URL: Generate a QR code for any URL:
</p> </p>
<div class="bg-slate-800 rounded-sm overflow-hidden mb-4"> <div class="bg-zinc-800 rounded-sm overflow-hidden mb-4">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700"> <div class="flex items-center justify-between px-4 py-2 border-b border-zinc-700">
<span class="text-sm text-slate-400">cURL</span> <span class="text-sm text-zinc-400">cURL</span>
</div> </div>
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300"><span class="text-teal-400">curl</span> <span class="text-slate-500">--request</span> POST \ <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300"><span class="text-teal-400">curl</span> <span class="text-zinc-500">--request</span> POST \
<span class="text-slate-500">--url</span> <span class="text-amber-400">'https://api.host.uk.com/api/v1/qr/generate'</span> \ <span class="text-zinc-500">--url</span> <span class="text-amber-400">'https://api.lthn.ai/api/v1/qr/generate'</span> \
<span class="text-slate-500">--header</span> <span class="text-amber-400">'Authorization: Bearer YOUR_API_KEY'</span> \ <span class="text-zinc-500">--header</span> <span class="text-amber-400">'Authorization: Bearer YOUR_API_KEY'</span> \
<span class="text-slate-500">--header</span> <span class="text-amber-400">'Content-Type: application/json'</span> \ <span class="text-zinc-500">--header</span> <span class="text-amber-400">'Content-Type: application/json'</span> \
<span class="text-slate-500">--data</span> <span class="text-amber-400">'{ <span class="text-zinc-500">--data</span> <span class="text-amber-400">'{
"url": "https://example.com", "url": "https://example.com",
"format": "svg", "format": "svg",
"size": 300 "size": 300
@ -123,40 +123,40 @@
{{-- Options --}} {{-- Options --}}
<section id="options" data-scrollspy-target class="mb-12"> <section id="options" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Customisation Options</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Customisation Options</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Available customisation parameters: Available customisation parameters:
</p> </p>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="w-full text-sm"> <table class="w-full text-sm">
<thead> <thead>
<tr class="border-b border-slate-200 dark:border-slate-700"> <tr class="border-b border-zinc-200 dark:border-zinc-700">
<th class="text-left py-3 px-4 font-medium text-slate-800 dark:text-slate-200">Parameter</th> <th class="text-left py-3 px-4 font-medium text-zinc-800 dark:text-zinc-200">Parameter</th>
<th class="text-left py-3 px-4 font-medium text-slate-800 dark:text-slate-200">Type</th> <th class="text-left py-3 px-4 font-medium text-zinc-800 dark:text-zinc-200">Type</th>
<th class="text-left py-3 px-4 font-medium text-slate-800 dark:text-slate-200">Description</th> <th class="text-left py-3 px-4 font-medium text-zinc-800 dark:text-zinc-200">Description</th>
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-slate-200 dark:divide-slate-700"> <tbody class="divide-y divide-zinc-200 dark:divide-zinc-700">
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">format</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">format</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">string</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">string</td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Output format: <code>svg</code> or <code>png</code></td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Output format: <code>svg</code> or <code>png</code></td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">size</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">size</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">integer</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">integer</td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Size in pixels (100-2000)</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Size in pixels (100-2000)</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">color</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">color</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">string</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">string</td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Foreground colour (hex)</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Foreground colour (hex)</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">background</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">background</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">string</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">string</td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Background colour (hex)</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Background colour (hex)</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -165,32 +165,32 @@
{{-- Download --}} {{-- Download --}}
<section id="download" data-scrollspy-target class="mb-12"> <section id="download" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Download Formats</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Download Formats</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Download QR codes directly as image files: Download QR codes directly as image files:
</p> </p>
<div class="bg-slate-800 rounded-sm overflow-hidden mb-4"> <div class="bg-zinc-800 rounded-sm overflow-hidden mb-4">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700"> <div class="flex items-center justify-between px-4 py-2 border-b border-zinc-700">
<span class="text-sm text-slate-400">cURL</span> <span class="text-sm text-zinc-400">cURL</span>
</div> </div>
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300"><span class="text-teal-400">curl</span> <span class="text-slate-500">--request</span> GET \ <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300"><span class="text-teal-400">curl</span> <span class="text-zinc-500">--request</span> GET \
<span class="text-slate-500">--url</span> <span class="text-amber-400">'https://api.host.uk.com/api/v1/bio/1/qr/download?format=png&size=500'</span> \ <span class="text-zinc-500">--url</span> <span class="text-amber-400">'https://api.lthn.ai/api/v1/bio/1/qr/download?format=png&size=500'</span> \
<span class="text-slate-500">--header</span> <span class="text-amber-400">'Authorization: Bearer YOUR_API_KEY'</span> \ <span class="text-zinc-500">--header</span> <span class="text-amber-400">'Authorization: Bearer YOUR_API_KEY'</span> \
<span class="text-slate-500">--output</span> <span class="text-amber-400">qrcode.png</span></code></pre> <span class="text-zinc-500">--output</span> <span class="text-amber-400">qrcode.png</span></code></pre>
</div> </div>
<p class="text-slate-600 dark:text-slate-400"> <p class="text-zinc-600 dark:text-zinc-400">
The response is binary image data with appropriate Content-Type header. The response is binary image data with appropriate Content-Type header.
</p> </p>
</section> </section>
{{-- Next steps --}} {{-- Next steps --}}
<div class="flex items-center justify-between pt-8 border-t border-slate-200 dark:border-slate-700"> <div class="flex items-center justify-between pt-8 border-t border-zinc-200 dark:border-zinc-700">
<a href="{{ route('api.guides.biolinks') }}" class="text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200"> <a href="{{ route('api.guides.authentication') }}" class="text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200">
&larr; Managing Biolinks &larr; Authentication
</a> </a>
<a href="{{ route('api.guides.webhooks') }}" class="text-blue-600 hover:text-blue-700 dark:hover:text-blue-500 font-medium"> <a href="{{ route('api.guides.webhooks') }}" class="text-cyan-600 hover:text-cyan-700 dark:hover:text-blue-500 font-medium">
Webhooks &rarr; Webhooks &rarr;
</a> </a>
</div> </div>

View file

@ -6,32 +6,32 @@
<div class="flex"> <div class="flex">
{{-- Sidebar --}} {{-- Sidebar --}}
<aside class="hidden lg:block fixed left-0 top-16 md:top-20 bottom-0 w-64 border-r border-slate-200 dark:border-slate-800"> <aside class="hidden lg:block fixed left-0 top-16 md:top-20 bottom-0 w-64 border-r border-zinc-200 dark:border-zinc-800">
<div class="h-full px-4 py-8 overflow-y-auto no-scrollbar"> <div class="h-full px-4 py-8 overflow-y-auto no-scrollbar">
<nav> <nav>
<ul class="space-y-2"> <ul class="space-y-2">
<li> <li>
<a href="#prerequisites" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#prerequisites" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Prerequisites Prerequisites
</a> </a>
</li> </li>
<li> <li>
<a href="#create-api-key" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#create-api-key" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Create an API Key Create an API Key
</a> </a>
</li> </li>
<li> <li>
<a href="#first-request" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#first-request" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Make Your First Request Make Your First Request
</a> </a>
</li> </li>
<li> <li>
<a href="#create-biolink" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#store-memory" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Create a Biolink Store a Memory
</a> </a>
</li> </li>
<li> <li>
<a href="#next-steps" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#next-steps" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Next Steps Next Steps
</a> </a>
</li> </li>
@ -47,24 +47,24 @@
{{-- Breadcrumb --}} {{-- Breadcrumb --}}
<nav class="mb-8"> <nav class="mb-8">
<ol class="flex items-center gap-2 text-sm"> <ol class="flex items-center gap-2 text-sm">
<li><a href="{{ route('api.guides') }}" class="text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200">Guides</a></li> <li><a href="{{ route('api.guides') }}" class="text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200">Guides</a></li>
<li class="text-slate-400">/</li> <li class="text-zinc-400">/</li>
<li class="text-slate-800 dark:text-slate-200">Quick Start</li> <li class="text-zinc-800 dark:text-zinc-200">Quick Start</li>
</ol> </ol>
</nav> </nav>
<h1 class="h1 mb-4 text-slate-800 dark:text-slate-100">Quick Start</h1> <h1 class="h1 mb-4 text-zinc-800 dark:text-zinc-100">Quick Start</h1>
<p class="text-xl text-slate-600 dark:text-slate-400 mb-12"> <p class="text-xl text-zinc-600 dark:text-zinc-400 mb-12">
Get up and running with the API in under 5 minutes. Get up and running with the API in under 5 minutes.
</p> </p>
{{-- Prerequisites --}} {{-- Prerequisites --}}
<section id="prerequisites" data-scrollspy-target class="mb-12"> <section id="prerequisites" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Prerequisites</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Prerequisites</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Before you begin, you'll need: Before you begin, you'll need:
</p> </p>
<ul class="list-disc list-inside space-y-2 text-slate-600 dark:text-slate-400 mb-4"> <ul class="list-disc list-inside space-y-2 text-zinc-600 dark:text-zinc-400 mb-4">
<li>An account with API access</li> <li>An account with API access</li>
<li>A workspace (created automatically on signup)</li> <li>A workspace (created automatically on signup)</li>
<li>cURL or any HTTP client</li> <li>cURL or any HTTP client</li>
@ -73,16 +73,16 @@
{{-- Create API Key --}} {{-- Create API Key --}}
<section id="create-api-key" data-scrollspy-target class="mb-12"> <section id="create-api-key" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Create an API Key</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Create an API Key</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Navigate to your workspace settings and create a new API key: Navigate to your workspace settings and create a new API key:
</p> </p>
<ol class="list-decimal list-inside space-y-2 text-slate-600 dark:text-slate-400 mb-6"> <ol class="list-decimal list-inside space-y-2 text-zinc-600 dark:text-zinc-400 mb-6">
<li>Go to <strong>Settings API Keys</strong></li> <li>Go to <strong>Settings &rarr; API Keys</strong></li>
<li>Click <strong>Create API Key</strong></li> <li>Click <strong>Create API Key</strong></li>
<li>Give it a name (e.g., "Development")</li> <li>Give it a name (e.g., "Development")</li>
<li>Select the scopes you need (read, write, delete)</li> <li>Select the scopes you need (read, write, delete)</li>
<li>Copy the key - it won't be shown again!</li> <li>Copy the key &mdash; it won't be shown again!</li>
</ol> </ol>
{{-- Note box --}} {{-- Note box --}}
@ -100,88 +100,87 @@
{{-- First Request --}} {{-- First Request --}}
<section id="first-request" data-scrollspy-target class="mb-12"> <section id="first-request" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Make Your First Request</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Make Your First Request</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Let's verify your API key by listing your workspaces: Let's verify your API key by searching agent memories:
</p> </p>
<div class="bg-slate-800 rounded-sm overflow-hidden mb-4"> <div class="bg-zinc-800 rounded-sm overflow-hidden mb-4">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700"> <div class="flex items-center justify-between px-4 py-2 border-b border-zinc-700">
<span class="text-sm text-slate-400">cURL</span> <span class="text-sm text-zinc-400">cURL</span>
</div> </div>
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300"><span class="text-teal-400">curl</span> <span class="text-slate-500">--request</span> GET \ <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300"><span class="text-teal-400">curl</span> <span class="text-zinc-500">--request</span> POST \
<span class="text-slate-500">--url</span> <span class="text-amber-400">'https://api.host.uk.com/api/v1/workspaces/current'</span> \ <span class="text-zinc-500">--url</span> <span class="text-amber-400">'https://api.lthn.ai/v1/brain/recall'</span> \
<span class="text-slate-500">--header</span> <span class="text-amber-400">'Authorization: Bearer YOUR_API_KEY'</span></code></pre> <span class="text-zinc-500">--header</span> <span class="text-amber-400">'Authorization: Bearer hk_your_api_key'</span> \
<span class="text-zinc-500">--header</span> <span class="text-amber-400">'Content-Type: application/json'</span> \
<span class="text-zinc-500">--data</span> <span class="text-amber-400">'{"query": "hello world"}'</span></code></pre>
</div> </div>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
You should receive a response like: You should receive a response like:
</p> </p>
<div class="bg-slate-800 rounded-sm overflow-hidden"> <div class="bg-zinc-800 rounded-sm overflow-hidden">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700"> <div class="flex items-center justify-between px-4 py-2 border-b border-zinc-700">
<span class="text-sm text-slate-400">Response</span> <span class="text-sm text-zinc-400">Response</span>
</div> </div>
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300">{ <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300">{
<span class="text-blue-400">"data"</span>: { <span class="text-blue-400">"memories"</span>: [],
<span class="text-blue-400">"id"</span>: <span class="text-amber-400">1</span>, <span class="text-blue-400">"scores"</span>: {}
<span class="text-blue-400">"name"</span>: <span class="text-green-400">"My Workspace"</span>,
<span class="text-blue-400">"slug"</span>: <span class="text-green-400">"my-workspace-abc123"</span>,
<span class="text-blue-400">"is_active"</span>: <span class="text-purple-400">true</span>
}
}</code></pre> }</code></pre>
</div> </div>
</section> </section>
{{-- Create Biolink --}} {{-- Store Memory --}}
<section id="create-biolink" data-scrollspy-target class="mb-12"> <section id="store-memory" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Create a Biolink</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Store a Memory</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Now let's create your first biolink programmatically: Now let's store your first memory in the brain:
</p> </p>
<div class="bg-slate-800 rounded-sm overflow-hidden mb-4"> <div class="bg-zinc-800 rounded-sm overflow-hidden mb-4">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700"> <div class="flex items-center justify-between px-4 py-2 border-b border-zinc-700">
<span class="text-sm text-slate-400">cURL</span> <span class="text-sm text-zinc-400">cURL</span>
</div> </div>
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300"><span class="text-teal-400">curl</span> <span class="text-slate-500">--request</span> POST \ <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300"><span class="text-teal-400">curl</span> <span class="text-zinc-500">--request</span> POST \
<span class="text-slate-500">--url</span> <span class="text-amber-400">'https://api.host.uk.com/api/v1/bio'</span> \ <span class="text-zinc-500">--url</span> <span class="text-amber-400">'https://api.lthn.ai/v1/brain/remember'</span> \
<span class="text-slate-500">--header</span> <span class="text-amber-400">'Authorization: Bearer YOUR_API_KEY'</span> \ <span class="text-zinc-500">--header</span> <span class="text-amber-400">'Authorization: Bearer hk_your_api_key'</span> \
<span class="text-slate-500">--header</span> <span class="text-amber-400">'Content-Type: application/json'</span> \ <span class="text-zinc-500">--header</span> <span class="text-amber-400">'Content-Type: application/json'</span> \
<span class="text-slate-500">--data</span> <span class="text-amber-400">'{ <span class="text-zinc-500">--data</span> <span class="text-amber-400">'{
"url": "mypage", "content": "Go uses structural typing",
"type": "biolink" "type": "fact",
"tags": ["go", "typing"]
}'</span></code></pre> }'</span></code></pre>
</div> </div>
<p class="text-slate-600 dark:text-slate-400"> <p class="text-zinc-600 dark:text-zinc-400">
This creates a new biolink page at your configured short URL. This stores a fact in the vector database. You can then recall it with a semantic query.
</p> </p>
</section> </section>
{{-- Next Steps --}} {{-- Next Steps --}}
<section id="next-steps" data-scrollspy-target class="mb-12"> <section id="next-steps" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Next Steps</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Next Steps</h2>
<p class="text-slate-600 dark:text-slate-400 mb-6"> <p class="text-zinc-600 dark:text-zinc-400 mb-6">
Now that you've made your first API calls, explore more: Now that you've made your first API calls, explore more:
</p> </p>
<div class="grid sm:grid-cols-2 gap-4"> <div class="grid sm:grid-cols-2 gap-4">
<a href="{{ route('api.guides.biolinks') }}" class="p-4 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-sm hover:border-blue-300 dark:hover:border-blue-600"> <a href="{{ route('api.guides.authentication') }}" class="p-4 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-sm hover:border-cyan-300 dark:hover:border-cyan-600">
<h3 class="font-medium text-slate-800 dark:text-slate-200 mb-1">Managing Biolinks</h3> <h3 class="font-medium text-zinc-800 dark:text-zinc-200 mb-1">Authentication</h3>
<p class="text-sm text-slate-600 dark:text-slate-400">Add blocks, update settings, and customise themes.</p> <p class="text-sm text-zinc-600 dark:text-zinc-400">API key scopes, security best practices.</p>
</a> </a>
<a href="{{ route('api.guides.qrcodes') }}" class="p-4 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-sm hover:border-blue-300 dark:hover:border-blue-600"> <a href="{{ route('api.reference') }}" class="p-4 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-sm hover:border-cyan-300 dark:hover:border-cyan-600">
<h3 class="font-medium text-slate-800 dark:text-slate-200 mb-1">QR Code Generation</h3> <h3 class="font-medium text-zinc-800 dark:text-zinc-200 mb-1">API Reference</h3>
<p class="text-sm text-slate-600 dark:text-slate-400">Generate customised QR codes for your biolinks.</p> <p class="text-sm text-zinc-600 dark:text-zinc-400">Complete documentation of all endpoints.</p>
</a> </a>
<a href="{{ route('api.reference') }}" class="p-4 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-sm hover:border-blue-300 dark:hover:border-blue-600"> <a href="{{ route('api.guides.webhooks') }}" class="p-4 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-sm hover:border-cyan-300 dark:hover:border-cyan-600">
<h3 class="font-medium text-slate-800 dark:text-slate-200 mb-1">API Reference</h3> <h3 class="font-medium text-zinc-800 dark:text-zinc-200 mb-1">Webhooks</h3>
<p class="text-sm text-slate-600 dark:text-slate-400">Complete documentation of all endpoints.</p> <p class="text-sm text-zinc-600 dark:text-zinc-400">Receive real-time event notifications.</p>
</a> </a>
<a href="{{ route('api.swagger') }}" class="p-4 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-sm hover:border-blue-300 dark:hover:border-blue-600"> <a href="{{ route('api.swagger') }}" class="p-4 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-sm hover:border-cyan-300 dark:hover:border-cyan-600">
<h3 class="font-medium text-slate-800 dark:text-slate-200 mb-1">Swagger UI</h3> <h3 class="font-medium text-zinc-800 dark:text-zinc-200 mb-1">Swagger UI</h3>
<p class="text-sm text-slate-600 dark:text-slate-400">Interactive API explorer with try-it-out.</p> <p class="text-sm text-zinc-600 dark:text-zinc-400">Interactive API explorer with try-it-out.</p>
</a> </a>
</div> </div>
</section> </section>

View file

@ -6,47 +6,47 @@
<div class="flex"> <div class="flex">
{{-- Sidebar --}} {{-- Sidebar --}}
<aside class="hidden lg:block fixed left-0 top-16 md:top-20 bottom-0 w-64 border-r border-slate-200 dark:border-slate-800"> <aside class="hidden lg:block fixed left-0 top-16 md:top-20 bottom-0 w-64 border-r border-zinc-200 dark:border-zinc-800">
<div class="h-full px-4 py-8 overflow-y-auto no-scrollbar"> <div class="h-full px-4 py-8 overflow-y-auto no-scrollbar">
<nav> <nav>
<ul class="space-y-2"> <ul class="space-y-2">
<li> <li>
<a href="#overview" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#overview" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Overview Overview
</a> </a>
</li> </li>
<li> <li>
<a href="#setup" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#setup" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Setup Setup
</a> </a>
</li> </li>
<li> <li>
<a href="#events" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#events" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Event Types Event Types
</a> </a>
</li> </li>
<li> <li>
<a href="#payload" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#payload" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Payload Format Payload Format
</a> </a>
</li> </li>
<li> <li>
<a href="#headers" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#headers" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Request Headers Request Headers
</a> </a>
</li> </li>
<li> <li>
<a href="#verification" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#verification" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Signature Verification Signature Verification
</a> </a>
</li> </li>
<li> <li>
<a href="#retry-policy" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#retry-policy" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Retry Policy Retry Policy
</a> </a>
</li> </li>
<li> <li>
<a href="#best-practices" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#best-practices" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Best Practices Best Practices
</a> </a>
</li> </li>
@ -62,24 +62,24 @@
{{-- Breadcrumb --}} {{-- Breadcrumb --}}
<nav class="mb-8"> <nav class="mb-8">
<ol class="flex items-center gap-2 text-sm"> <ol class="flex items-center gap-2 text-sm">
<li><a href="{{ route('api.guides') }}" class="text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200">Guides</a></li> <li><a href="{{ route('api.guides') }}" class="text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200">Guides</a></li>
<li class="text-slate-400">/</li> <li class="text-zinc-400">/</li>
<li class="text-slate-800 dark:text-slate-200">Webhooks</li> <li class="text-zinc-800 dark:text-zinc-200">Webhooks</li>
</ol> </ol>
</nav> </nav>
<h1 class="h1 mb-4 text-slate-800 dark:text-slate-100">Webhooks</h1> <h1 class="h1 mb-4 text-zinc-800 dark:text-zinc-100">Webhooks</h1>
<p class="text-xl text-slate-600 dark:text-slate-400 mb-12"> <p class="text-xl text-zinc-600 dark:text-zinc-400 mb-12">
Receive real-time notifications for events in your workspace with cryptographically signed payloads. Receive real-time notifications for events in your workspace with cryptographically signed payloads.
</p> </p>
{{-- Overview --}} {{-- Overview --}}
<section id="overview" data-scrollspy-target class="mb-12"> <section id="overview" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Overview</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Overview</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Webhooks allow your application to receive real-time HTTP callbacks when events occur in your workspace. Instead of polling the API, webhooks push data to your server as events happen. Webhooks allow your application to receive real-time HTTP callbacks when events occur in your workspace. Instead of polling the API, webhooks push data to your server as events happen.
</p> </p>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
All webhook requests are cryptographically signed using HMAC-SHA256, allowing you to verify that requests genuinely came from our platform and haven't been tampered with. All webhook requests are cryptographically signed using HMAC-SHA256, allowing you to verify that requests genuinely came from our platform and haven't been tampered with.
</p> </p>
<div class="text-sm p-4 bg-amber-50 border border-amber-200 rounded-sm dark:bg-amber-900/20 dark:border-amber-800"> <div class="text-sm p-4 bg-amber-50 border border-amber-200 rounded-sm dark:bg-amber-900/20 dark:border-amber-800">
@ -96,11 +96,11 @@
{{-- Setup --}} {{-- Setup --}}
<section id="setup" data-scrollspy-target class="mb-12"> <section id="setup" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Setup</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Setup</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
To configure webhooks: To configure webhooks:
</p> </p>
<ol class="list-decimal list-inside space-y-2 text-slate-600 dark:text-slate-400 mb-4"> <ol class="list-decimal list-inside space-y-2 text-zinc-600 dark:text-zinc-400 mb-4">
<li>Go to <strong>Settings &rarr; Webhooks</strong> in your workspace</li> <li>Go to <strong>Settings &rarr; Webhooks</strong> in your workspace</li>
<li>Click <strong>Add Webhook</strong></li> <li>Click <strong>Add Webhook</strong></li>
<li>Enter your endpoint URL (must be HTTPS in production)</li> <li>Enter your endpoint URL (must be HTTPS in production)</li>
@ -121,51 +121,51 @@
{{-- Events --}} {{-- Events --}}
<section id="events" data-scrollspy-target class="mb-12"> <section id="events" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Event Types</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Event Types</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Available webhook events: Available webhook events:
</p> </p>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="w-full text-sm"> <table class="w-full text-sm">
<thead> <thead>
<tr class="border-b border-slate-200 dark:border-slate-700"> <tr class="border-b border-zinc-200 dark:border-zinc-700">
<th class="text-left py-3 px-4 font-medium text-slate-800 dark:text-slate-200">Event</th> <th class="text-left py-3 px-4 font-medium text-zinc-800 dark:text-zinc-200">Event</th>
<th class="text-left py-3 px-4 font-medium text-slate-800 dark:text-slate-200">Description</th> <th class="text-left py-3 px-4 font-medium text-zinc-800 dark:text-zinc-200">Description</th>
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-slate-200 dark:divide-slate-700"> <tbody class="divide-y divide-zinc-200 dark:divide-zinc-700">
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">bio.created</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">bio.created</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">A new biolink was created</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">A new biolink was created</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">bio.updated</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">bio.updated</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">A biolink was updated</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">A biolink was updated</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">bio.deleted</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">bio.deleted</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">A biolink was deleted</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">A biolink was deleted</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">link.created</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">link.created</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">A new link was created</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">A new link was created</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">link.clicked</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">link.clicked</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">A link was clicked (high volume)</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">A link was clicked (high volume)</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">qrcode.created</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">qrcode.created</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">A QR code was generated</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">A QR code was generated</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">qrcode.scanned</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">qrcode.scanned</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">A QR code was scanned (high volume)</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">A QR code was scanned (high volume)</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">*</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">*</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Subscribe to all events (wildcard)</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Subscribe to all events (wildcard)</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -174,13 +174,13 @@
{{-- Payload --}} {{-- Payload --}}
<section id="payload" data-scrollspy-target class="mb-12"> <section id="payload" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Payload Format</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Payload Format</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Webhook payloads are sent as JSON with a consistent structure: Webhook payloads are sent as JSON with a consistent structure:
</p> </p>
<div class="bg-slate-800 rounded-sm overflow-hidden"> <div class="bg-zinc-800 rounded-sm overflow-hidden">
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300">{ <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300">{
<span class="text-blue-400">"id"</span>: <span class="text-green-400">"evt_abc123xyz456"</span>, <span class="text-blue-400">"id"</span>: <span class="text-green-400">"evt_abc123xyz456"</span>,
<span class="text-blue-400">"type"</span>: <span class="text-green-400">"bio.created"</span>, <span class="text-blue-400">"type"</span>: <span class="text-green-400">"bio.created"</span>,
<span class="text-blue-400">"created_at"</span>: <span class="text-green-400">"2024-01-15T10:30:00Z"</span>, <span class="text-blue-400">"created_at"</span>: <span class="text-green-400">"2024-01-15T10:30:00Z"</span>,
@ -196,39 +196,39 @@
{{-- Headers --}} {{-- Headers --}}
<section id="headers" data-scrollspy-target class="mb-12"> <section id="headers" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Request Headers</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Request Headers</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Every webhook request includes the following headers: Every webhook request includes the following headers:
</p> </p>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="w-full text-sm"> <table class="w-full text-sm">
<thead> <thead>
<tr class="border-b border-slate-200 dark:border-slate-700"> <tr class="border-b border-zinc-200 dark:border-zinc-700">
<th class="text-left py-3 px-4 font-medium text-slate-800 dark:text-slate-200">Header</th> <th class="text-left py-3 px-4 font-medium text-zinc-800 dark:text-zinc-200">Header</th>
<th class="text-left py-3 px-4 font-medium text-slate-800 dark:text-slate-200">Description</th> <th class="text-left py-3 px-4 font-medium text-zinc-800 dark:text-zinc-200">Description</th>
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-slate-200 dark:divide-slate-700"> <tbody class="divide-y divide-zinc-200 dark:divide-zinc-700">
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">X-Webhook-Signature</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">X-Webhook-Signature</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">HMAC-SHA256 signature for verification</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">HMAC-SHA256 signature for verification</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">X-Webhook-Timestamp</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">X-Webhook-Timestamp</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Unix timestamp when the webhook was sent</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Unix timestamp when the webhook was sent</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">X-Webhook-Event</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">X-Webhook-Event</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">The event type (e.g., <code>bio.created</code>)</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">The event type (e.g., <code>bio.created</code>)</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">X-Webhook-Id</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">X-Webhook-Id</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Unique delivery ID for idempotency</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Unique delivery ID for idempotency</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-slate-100 dark:bg-slate-800 rounded text-xs">Content-Type</code></td> <td class="py-3 px-4"><code class="px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded text-xs">Content-Type</code></td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Always <code>application/json</code></td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Always <code>application/json</code></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -237,13 +237,13 @@
{{-- Verification --}} {{-- Verification --}}
<section id="verification" data-scrollspy-target class="mb-12"> <section id="verification" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Signature Verification</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Signature Verification</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
To verify a webhook signature, compute the HMAC-SHA256 of the timestamp concatenated with the raw request body using your webhook secret. The signature includes the timestamp to prevent replay attacks. To verify a webhook signature, compute the HMAC-SHA256 of the timestamp concatenated with the raw request body using your webhook secret. The signature includes the timestamp to prevent replay attacks.
</p> </p>
<h3 class="h4 mb-3 text-slate-800 dark:text-slate-100">Verification Algorithm</h3> <h3 class="h4 mb-3 text-zinc-800 dark:text-zinc-100">Verification Algorithm</h3>
<ol class="list-decimal list-inside space-y-2 text-slate-600 dark:text-slate-400 mb-6"> <ol class="list-decimal list-inside space-y-2 text-zinc-600 dark:text-zinc-400 mb-6">
<li>Extract <code>X-Webhook-Signature</code> and <code>X-Webhook-Timestamp</code> headers</li> <li>Extract <code>X-Webhook-Signature</code> and <code>X-Webhook-Timestamp</code> headers</li>
<li>Concatenate: <code>timestamp + "." + raw_request_body</code></li> <li>Concatenate: <code>timestamp + "." + raw_request_body</code></li>
<li>Compute: <code>HMAC-SHA256(concatenated_string, your_webhook_secret)</code></li> <li>Compute: <code>HMAC-SHA256(concatenated_string, your_webhook_secret)</code></li>
@ -252,75 +252,75 @@
</ol> </ol>
{{-- PHP Example --}} {{-- PHP Example --}}
<h3 class="h4 mb-3 text-slate-800 dark:text-slate-100">PHP</h3> <h3 class="h4 mb-3 text-zinc-800 dark:text-zinc-100">PHP</h3>
<div class="bg-slate-800 rounded-sm overflow-hidden mb-6"> <div class="bg-zinc-800 rounded-sm overflow-hidden mb-6">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700"> <div class="flex items-center justify-between px-4 py-2 border-b border-zinc-700">
<span class="text-sm text-slate-400">webhook-handler.php</span> <span class="text-sm text-zinc-400">webhook-handler.php</span>
</div> </div>
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300"><span class="text-purple-400">&lt;?php</span> <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300"><span class="text-purple-400">&lt;?php</span>
<span class="text-slate-500">// Get request data</span> <span class="text-zinc-500">// Get request data</span>
<span class="text-purple-400">$payload</span> = <span class="text-teal-400">file_get_contents</span>(<span class="text-green-400">'php://input'</span>); <span class="text-purple-400">$payload</span> = <span class="text-teal-400">file_get_contents</span>(<span class="text-green-400">'php://input'</span>);
<span class="text-purple-400">$signature</span> = <span class="text-purple-400">$_SERVER</span>[<span class="text-green-400">'HTTP_X_WEBHOOK_SIGNATURE'</span>] ?? <span class="text-green-400">''</span>; <span class="text-purple-400">$signature</span> = <span class="text-purple-400">$_SERVER</span>[<span class="text-green-400">'HTTP_X_WEBHOOK_SIGNATURE'</span>] ?? <span class="text-green-400">''</span>;
<span class="text-purple-400">$timestamp</span> = <span class="text-purple-400">$_SERVER</span>[<span class="text-green-400">'HTTP_X_WEBHOOK_TIMESTAMP'</span>] ?? <span class="text-green-400">''</span>; <span class="text-purple-400">$timestamp</span> = <span class="text-purple-400">$_SERVER</span>[<span class="text-green-400">'HTTP_X_WEBHOOK_TIMESTAMP'</span>] ?? <span class="text-green-400">''</span>;
<span class="text-purple-400">$secret</span> = <span class="text-teal-400">getenv</span>(<span class="text-green-400">'WEBHOOK_SECRET'</span>); <span class="text-purple-400">$secret</span> = <span class="text-teal-400">getenv</span>(<span class="text-green-400">'WEBHOOK_SECRET'</span>);
<span class="text-slate-500">// Verify timestamp (5 minute tolerance)</span> <span class="text-zinc-500">// Verify timestamp (5 minute tolerance)</span>
<span class="text-purple-400">$tolerance</span> = <span class="text-amber-400">300</span>; <span class="text-purple-400">$tolerance</span> = <span class="text-amber-400">300</span>;
<span class="text-pink-400">if</span> (<span class="text-teal-400">abs</span>(<span class="text-teal-400">time</span>() - (<span class="text-pink-400">int</span>)<span class="text-purple-400">$timestamp</span>) > <span class="text-purple-400">$tolerance</span>) { <span class="text-pink-400">if</span> (<span class="text-teal-400">abs</span>(<span class="text-teal-400">time</span>() - (<span class="text-pink-400">int</span>)<span class="text-purple-400">$timestamp</span>) > <span class="text-purple-400">$tolerance</span>) {
<span class="text-teal-400">http_response_code</span>(<span class="text-amber-400">401</span>); <span class="text-teal-400">http_response_code</span>(<span class="text-amber-400">401</span>);
<span class="text-pink-400">die</span>(<span class="text-green-400">'Webhook timestamp expired'</span>); <span class="text-pink-400">die</span>(<span class="text-green-400">'Webhook timestamp expired'</span>);
} }
<span class="text-slate-500">// Compute expected signature</span> <span class="text-zinc-500">// Compute expected signature</span>
<span class="text-purple-400">$signedPayload</span> = <span class="text-purple-400">$timestamp</span> . <span class="text-green-400">'.'</span> . <span class="text-purple-400">$payload</span>; <span class="text-purple-400">$signedPayload</span> = <span class="text-purple-400">$timestamp</span> . <span class="text-green-400">'.'</span> . <span class="text-purple-400">$payload</span>;
<span class="text-purple-400">$expectedSignature</span> = <span class="text-teal-400">hash_hmac</span>(<span class="text-green-400">'sha256'</span>, <span class="text-purple-400">$signedPayload</span>, <span class="text-purple-400">$secret</span>); <span class="text-purple-400">$expectedSignature</span> = <span class="text-teal-400">hash_hmac</span>(<span class="text-green-400">'sha256'</span>, <span class="text-purple-400">$signedPayload</span>, <span class="text-purple-400">$secret</span>);
<span class="text-slate-500">// Verify signature (timing-safe comparison)</span> <span class="text-zinc-500">// Verify signature (timing-safe comparison)</span>
<span class="text-pink-400">if</span> (!<span class="text-teal-400">hash_equals</span>(<span class="text-purple-400">$expectedSignature</span>, <span class="text-purple-400">$signature</span>)) { <span class="text-pink-400">if</span> (!<span class="text-teal-400">hash_equals</span>(<span class="text-purple-400">$expectedSignature</span>, <span class="text-purple-400">$signature</span>)) {
<span class="text-teal-400">http_response_code</span>(<span class="text-amber-400">401</span>); <span class="text-teal-400">http_response_code</span>(<span class="text-amber-400">401</span>);
<span class="text-pink-400">die</span>(<span class="text-green-400">'Invalid webhook signature'</span>); <span class="text-pink-400">die</span>(<span class="text-green-400">'Invalid webhook signature'</span>);
} }
<span class="text-slate-500">// Signature valid - process the webhook</span> <span class="text-zinc-500">// Signature valid - process the webhook</span>
<span class="text-purple-400">$event</span> = <span class="text-teal-400">json_decode</span>(<span class="text-purple-400">$payload</span>, <span class="text-pink-400">true</span>); <span class="text-purple-400">$event</span> = <span class="text-teal-400">json_decode</span>(<span class="text-purple-400">$payload</span>, <span class="text-pink-400">true</span>);
<span class="text-teal-400">processWebhook</span>(<span class="text-purple-400">$event</span>);</code></pre> <span class="text-teal-400">processWebhook</span>(<span class="text-purple-400">$event</span>);</code></pre>
</div> </div>
{{-- Node.js Example --}} {{-- Node.js Example --}}
<h3 class="h4 mb-3 text-slate-800 dark:text-slate-100">Node.js</h3> <h3 class="h4 mb-3 text-zinc-800 dark:text-zinc-100">Node.js</h3>
<div class="bg-slate-800 rounded-sm overflow-hidden mb-6"> <div class="bg-zinc-800 rounded-sm overflow-hidden mb-6">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700"> <div class="flex items-center justify-between px-4 py-2 border-b border-zinc-700">
<span class="text-sm text-slate-400">webhook-handler.js</span> <span class="text-sm text-zinc-400">webhook-handler.js</span>
</div> </div>
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300"><span class="text-pink-400">const</span> crypto = <span class="text-teal-400">require</span>(<span class="text-green-400">'crypto'</span>); <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300"><span class="text-pink-400">const</span> crypto = <span class="text-teal-400">require</span>(<span class="text-green-400">'crypto'</span>);
<span class="text-pink-400">const</span> express = <span class="text-teal-400">require</span>(<span class="text-green-400">'express'</span>); <span class="text-pink-400">const</span> express = <span class="text-teal-400">require</span>(<span class="text-green-400">'express'</span>);
<span class="text-pink-400">const</span> app = <span class="text-teal-400">express</span>(); <span class="text-pink-400">const</span> app = <span class="text-teal-400">express</span>();
app.<span class="text-teal-400">use</span>(express.<span class="text-teal-400">raw</span>({ type: <span class="text-green-400">'application/json'</span> })); app.<span class="text-teal-400">use</span>(express.<span class="text-teal-400">raw</span>({ type: <span class="text-green-400">'application/json'</span> }));
<span class="text-pink-400">const</span> WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; <span class="text-pink-400">const</span> WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
<span class="text-pink-400">const</span> TOLERANCE = <span class="text-amber-400">300</span>; <span class="text-slate-500">// 5 minutes</span> <span class="text-pink-400">const</span> TOLERANCE = <span class="text-amber-400">300</span>; <span class="text-zinc-500">// 5 minutes</span>
app.<span class="text-teal-400">post</span>(<span class="text-green-400">'/webhook'</span>, (req, res) => { app.<span class="text-teal-400">post</span>(<span class="text-green-400">'/webhook'</span>, (req, res) => {
<span class="text-pink-400">const</span> signature = req.headers[<span class="text-green-400">'x-webhook-signature'</span>]; <span class="text-pink-400">const</span> signature = req.headers[<span class="text-green-400">'x-webhook-signature'</span>];
<span class="text-pink-400">const</span> timestamp = req.headers[<span class="text-green-400">'x-webhook-timestamp'</span>]; <span class="text-pink-400">const</span> timestamp = req.headers[<span class="text-green-400">'x-webhook-timestamp'</span>];
<span class="text-pink-400">const</span> payload = req.body; <span class="text-pink-400">const</span> payload = req.body;
<span class="text-slate-500">// Verify timestamp</span> <span class="text-zinc-500">// Verify timestamp</span>
<span class="text-pink-400">const</span> now = Math.<span class="text-teal-400">floor</span>(Date.<span class="text-teal-400">now</span>() / <span class="text-amber-400">1000</span>); <span class="text-pink-400">const</span> now = Math.<span class="text-teal-400">floor</span>(Date.<span class="text-teal-400">now</span>() / <span class="text-amber-400">1000</span>);
<span class="text-pink-400">if</span> (Math.<span class="text-teal-400">abs</span>(now - <span class="text-teal-400">parseInt</span>(timestamp)) > TOLERANCE) { <span class="text-pink-400">if</span> (Math.<span class="text-teal-400">abs</span>(now - <span class="text-teal-400">parseInt</span>(timestamp)) > TOLERANCE) {
<span class="text-pink-400">return</span> res.<span class="text-teal-400">status</span>(<span class="text-amber-400">401</span>).<span class="text-teal-400">send</span>(<span class="text-green-400">'Webhook timestamp expired'</span>); <span class="text-pink-400">return</span> res.<span class="text-teal-400">status</span>(<span class="text-amber-400">401</span>).<span class="text-teal-400">send</span>(<span class="text-green-400">'Webhook timestamp expired'</span>);
} }
<span class="text-slate-500">// Compute expected signature</span> <span class="text-zinc-500">// Compute expected signature</span>
<span class="text-pink-400">const</span> signedPayload = <span class="text-green-400">`${</span>timestamp<span class="text-green-400">}.${</span>payload<span class="text-green-400">}`</span>; <span class="text-pink-400">const</span> signedPayload = <span class="text-green-400">`${</span>timestamp<span class="text-green-400">}.${</span>payload<span class="text-green-400">}`</span>;
<span class="text-pink-400">const</span> expectedSignature = crypto <span class="text-pink-400">const</span> expectedSignature = crypto
.<span class="text-teal-400">createHmac</span>(<span class="text-green-400">'sha256'</span>, WEBHOOK_SECRET) .<span class="text-teal-400">createHmac</span>(<span class="text-green-400">'sha256'</span>, WEBHOOK_SECRET)
.<span class="text-teal-400">update</span>(signedPayload) .<span class="text-teal-400">update</span>(signedPayload)
.<span class="text-teal-400">digest</span>(<span class="text-green-400">'hex'</span>); .<span class="text-teal-400">digest</span>(<span class="text-green-400">'hex'</span>);
<span class="text-slate-500">// Verify signature (timing-safe comparison)</span> <span class="text-zinc-500">// Verify signature (timing-safe comparison)</span>
<span class="text-pink-400">if</span> (!crypto.<span class="text-teal-400">timingSafeEqual</span>( <span class="text-pink-400">if</span> (!crypto.<span class="text-teal-400">timingSafeEqual</span>(
Buffer.<span class="text-teal-400">from</span>(expectedSignature), Buffer.<span class="text-teal-400">from</span>(expectedSignature),
Buffer.<span class="text-teal-400">from</span>(signature) Buffer.<span class="text-teal-400">from</span>(signature)
@ -328,7 +328,7 @@ app.<span class="text-teal-400">post</span>(<span class="text-green-400">'/webho
<span class="text-pink-400">return</span> res.<span class="text-teal-400">status</span>(<span class="text-amber-400">401</span>).<span class="text-teal-400">send</span>(<span class="text-green-400">'Invalid webhook signature'</span>); <span class="text-pink-400">return</span> res.<span class="text-teal-400">status</span>(<span class="text-amber-400">401</span>).<span class="text-teal-400">send</span>(<span class="text-green-400">'Invalid webhook signature'</span>);
} }
<span class="text-slate-500">// Signature valid - process the webhook</span> <span class="text-zinc-500">// Signature valid - process the webhook</span>
<span class="text-pink-400">const</span> event = JSON.<span class="text-teal-400">parse</span>(payload); <span class="text-pink-400">const</span> event = JSON.<span class="text-teal-400">parse</span>(payload);
<span class="text-teal-400">processWebhook</span>(event); <span class="text-teal-400">processWebhook</span>(event);
res.<span class="text-teal-400">status</span>(<span class="text-amber-400">200</span>).<span class="text-teal-400">send</span>(<span class="text-green-400">'OK'</span>); res.<span class="text-teal-400">status</span>(<span class="text-amber-400">200</span>).<span class="text-teal-400">send</span>(<span class="text-green-400">'OK'</span>);
@ -336,12 +336,12 @@ app.<span class="text-teal-400">post</span>(<span class="text-green-400">'/webho
</div> </div>
{{-- Python Example --}} {{-- Python Example --}}
<h3 class="h4 mb-3 text-slate-800 dark:text-slate-100">Python</h3> <h3 class="h4 mb-3 text-zinc-800 dark:text-zinc-100">Python</h3>
<div class="bg-slate-800 rounded-sm overflow-hidden mb-6"> <div class="bg-zinc-800 rounded-sm overflow-hidden mb-6">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700"> <div class="flex items-center justify-between px-4 py-2 border-b border-zinc-700">
<span class="text-sm text-slate-400">webhook_handler.py</span> <span class="text-sm text-zinc-400">webhook_handler.py</span>
</div> </div>
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300"><span class="text-pink-400">import</span> hmac <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300"><span class="text-pink-400">import</span> hmac
<span class="text-pink-400">import</span> hashlib <span class="text-pink-400">import</span> hashlib
<span class="text-pink-400">import</span> time <span class="text-pink-400">import</span> time
<span class="text-pink-400">import</span> os <span class="text-pink-400">import</span> os
@ -349,7 +349,7 @@ app.<span class="text-teal-400">post</span>(<span class="text-green-400">'/webho
app = Flask(__name__) app = Flask(__name__)
WEBHOOK_SECRET = os.environ[<span class="text-green-400">'WEBHOOK_SECRET'</span>] WEBHOOK_SECRET = os.environ[<span class="text-green-400">'WEBHOOK_SECRET'</span>]
TOLERANCE = <span class="text-amber-400">300</span> <span class="text-slate-500"># 5 minutes</span> TOLERANCE = <span class="text-amber-400">300</span> <span class="text-zinc-500"># 5 minutes</span>
<span class="text-pink-400">@</span>app.route(<span class="text-green-400">'/webhook'</span>, methods=[<span class="text-green-400">'POST'</span>]) <span class="text-pink-400">@</span>app.route(<span class="text-green-400">'/webhook'</span>, methods=[<span class="text-green-400">'POST'</span>])
<span class="text-pink-400">def</span> <span class="text-teal-400">webhook</span>(): <span class="text-pink-400">def</span> <span class="text-teal-400">webhook</span>():
@ -357,11 +357,11 @@ TOLERANCE = <span class="text-amber-400">300</span> <span class="text-slate-500
timestamp = request.headers.get(<span class="text-green-400">'X-Webhook-Timestamp'</span>, <span class="text-green-400">''</span>) timestamp = request.headers.get(<span class="text-green-400">'X-Webhook-Timestamp'</span>, <span class="text-green-400">''</span>)
payload = request.get_data(as_text=<span class="text-pink-400">True</span>) payload = request.get_data(as_text=<span class="text-pink-400">True</span>)
<span class="text-slate-500"># Verify timestamp</span> <span class="text-zinc-500"># Verify timestamp</span>
<span class="text-pink-400">if</span> abs(time.time() - int(timestamp)) > TOLERANCE: <span class="text-pink-400">if</span> abs(time.time() - int(timestamp)) > TOLERANCE:
abort(<span class="text-amber-400">401</span>, <span class="text-green-400">'Webhook timestamp expired'</span>) abort(<span class="text-amber-400">401</span>, <span class="text-green-400">'Webhook timestamp expired'</span>)
<span class="text-slate-500"># Compute expected signature</span> <span class="text-zinc-500"># Compute expected signature</span>
signed_payload = <span class="text-green-400">f'{timestamp}.{payload}'</span> signed_payload = <span class="text-green-400">f'{timestamp}.{payload}'</span>
expected_signature = hmac.new( expected_signature = hmac.new(
WEBHOOK_SECRET.encode(), WEBHOOK_SECRET.encode(),
@ -369,40 +369,40 @@ TOLERANCE = <span class="text-amber-400">300</span> <span class="text-slate-500
hashlib.sha256 hashlib.sha256
).hexdigest() ).hexdigest()
<span class="text-slate-500"># Verify signature (timing-safe comparison)</span> <span class="text-zinc-500"># Verify signature (timing-safe comparison)</span>
<span class="text-pink-400">if not</span> hmac.compare_digest(expected_signature, signature): <span class="text-pink-400">if not</span> hmac.compare_digest(expected_signature, signature):
abort(<span class="text-amber-400">401</span>, <span class="text-green-400">'Invalid webhook signature'</span>) abort(<span class="text-amber-400">401</span>, <span class="text-green-400">'Invalid webhook signature'</span>)
<span class="text-slate-500"># Signature valid - process the webhook</span> <span class="text-zinc-500"># Signature valid - process the webhook</span>
event = request.get_json() event = request.get_json()
process_webhook(event) process_webhook(event)
<span class="text-pink-400">return</span> <span class="text-green-400">'OK'</span>, <span class="text-amber-400">200</span></code></pre> <span class="text-pink-400">return</span> <span class="text-green-400">'OK'</span>, <span class="text-amber-400">200</span></code></pre>
</div> </div>
{{-- Ruby Example --}} {{-- Ruby Example --}}
<h3 class="h4 mb-3 text-slate-800 dark:text-slate-100">Ruby</h3> <h3 class="h4 mb-3 text-zinc-800 dark:text-zinc-100">Ruby</h3>
<div class="bg-slate-800 rounded-sm overflow-hidden mb-6"> <div class="bg-zinc-800 rounded-sm overflow-hidden mb-6">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700"> <div class="flex items-center justify-between px-4 py-2 border-b border-zinc-700">
<span class="text-sm text-slate-400">webhook_handler.rb</span> <span class="text-sm text-zinc-400">webhook_handler.rb</span>
</div> </div>
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300"><span class="text-pink-400">require</span> <span class="text-green-400">'sinatra'</span> <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300"><span class="text-pink-400">require</span> <span class="text-green-400">'sinatra'</span>
<span class="text-pink-400">require</span> <span class="text-green-400">'openssl'</span> <span class="text-pink-400">require</span> <span class="text-green-400">'openssl'</span>
<span class="text-pink-400">require</span> <span class="text-green-400">'json'</span> <span class="text-pink-400">require</span> <span class="text-green-400">'json'</span>
WEBHOOK_SECRET = ENV[<span class="text-green-400">'WEBHOOK_SECRET'</span>] WEBHOOK_SECRET = ENV[<span class="text-green-400">'WEBHOOK_SECRET'</span>]
TOLERANCE = <span class="text-amber-400">300</span> <span class="text-slate-500"># 5 minutes</span> TOLERANCE = <span class="text-amber-400">300</span> <span class="text-zinc-500"># 5 minutes</span>
post <span class="text-green-400">'/webhook'</span> <span class="text-pink-400">do</span> post <span class="text-green-400">'/webhook'</span> <span class="text-pink-400">do</span>
signature = request.env[<span class="text-green-400">'HTTP_X_WEBHOOK_SIGNATURE'</span>] || <span class="text-green-400">''</span> signature = request.env[<span class="text-green-400">'HTTP_X_WEBHOOK_SIGNATURE'</span>] || <span class="text-green-400">''</span>
timestamp = request.env[<span class="text-green-400">'HTTP_X_WEBHOOK_TIMESTAMP'</span>] || <span class="text-green-400">''</span> timestamp = request.env[<span class="text-green-400">'HTTP_X_WEBHOOK_TIMESTAMP'</span>] || <span class="text-green-400">''</span>
payload = request.body.read payload = request.body.read
<span class="text-slate-500"># Verify timestamp</span> <span class="text-zinc-500"># Verify timestamp</span>
<span class="text-pink-400">if</span> (Time.now.to_i - timestamp.to_i).<span class="text-teal-400">abs</span> > TOLERANCE <span class="text-pink-400">if</span> (Time.now.to_i - timestamp.to_i).<span class="text-teal-400">abs</span> > TOLERANCE
halt <span class="text-amber-400">401</span>, <span class="text-green-400">'Webhook timestamp expired'</span> halt <span class="text-amber-400">401</span>, <span class="text-green-400">'Webhook timestamp expired'</span>
<span class="text-pink-400">end</span> <span class="text-pink-400">end</span>
<span class="text-slate-500"># Compute expected signature</span> <span class="text-zinc-500"># Compute expected signature</span>
signed_payload = <span class="text-green-400">"#{timestamp}.#{payload}"</span> signed_payload = <span class="text-green-400">"#{timestamp}.#{payload}"</span>
expected_signature = OpenSSL::HMAC.hexdigest( expected_signature = OpenSSL::HMAC.hexdigest(
<span class="text-green-400">'sha256'</span>, <span class="text-green-400">'sha256'</span>,
@ -410,12 +410,12 @@ post <span class="text-green-400">'/webhook'</span> <span class="text-pink-400">
signed_payload signed_payload
) )
<span class="text-slate-500"># Verify signature (timing-safe comparison)</span> <span class="text-zinc-500"># Verify signature (timing-safe comparison)</span>
<span class="text-pink-400">unless</span> Rack::Utils.secure_compare(expected_signature, signature) <span class="text-pink-400">unless</span> Rack::Utils.secure_compare(expected_signature, signature)
halt <span class="text-amber-400">401</span>, <span class="text-green-400">'Invalid webhook signature'</span> halt <span class="text-amber-400">401</span>, <span class="text-green-400">'Invalid webhook signature'</span>
<span class="text-pink-400">end</span> <span class="text-pink-400">end</span>
<span class="text-slate-500"># Signature valid - process the webhook</span> <span class="text-zinc-500"># Signature valid - process the webhook</span>
event = JSON.parse(payload) event = JSON.parse(payload)
process_webhook(event) process_webhook(event)
<span class="text-amber-400">200</span> <span class="text-amber-400">200</span>
@ -423,12 +423,12 @@ post <span class="text-green-400">'/webhook'</span> <span class="text-pink-400">
</div> </div>
{{-- Go Example --}} {{-- Go Example --}}
<h3 class="h4 mb-3 text-slate-800 dark:text-slate-100">Go</h3> <h3 class="h4 mb-3 text-zinc-800 dark:text-zinc-100">Go</h3>
<div class="bg-slate-800 rounded-sm overflow-hidden"> <div class="bg-zinc-800 rounded-sm overflow-hidden">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700"> <div class="flex items-center justify-between px-4 py-2 border-b border-zinc-700">
<span class="text-sm text-slate-400">webhook_handler.go</span> <span class="text-sm text-zinc-400">webhook_handler.go</span>
</div> </div>
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300"><span class="text-pink-400">package</span> main <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300"><span class="text-pink-400">package</span> main
<span class="text-pink-400">import</span> ( <span class="text-pink-400">import</span> (
<span class="text-green-400">"crypto/hmac"</span> <span class="text-green-400">"crypto/hmac"</span>
@ -443,7 +443,7 @@ post <span class="text-green-400">'/webhook'</span> <span class="text-pink-400">
<span class="text-green-400">"time"</span> <span class="text-green-400">"time"</span>
) )
<span class="text-pink-400">const</span> tolerance = <span class="text-amber-400">300</span> <span class="text-slate-500">// 5 minutes</span> <span class="text-pink-400">const</span> tolerance = <span class="text-amber-400">300</span> <span class="text-zinc-500">// 5 minutes</span>
<span class="text-pink-400">func</span> <span class="text-teal-400">webhookHandler</span>(w http.ResponseWriter, r *http.Request) { <span class="text-pink-400">func</span> <span class="text-teal-400">webhookHandler</span>(w http.ResponseWriter, r *http.Request) {
signature := r.Header.Get(<span class="text-green-400">"X-Webhook-Signature"</span>) signature := r.Header.Get(<span class="text-green-400">"X-Webhook-Signature"</span>)
@ -452,20 +452,20 @@ post <span class="text-green-400">'/webhook'</span> <span class="text-pink-400">
payload, _ := io.ReadAll(r.Body) payload, _ := io.ReadAll(r.Body)
<span class="text-slate-500">// Verify timestamp</span> <span class="text-zinc-500">// Verify timestamp</span>
ts, _ := strconv.ParseInt(timestamp, <span class="text-amber-400">10</span>, <span class="text-amber-400">64</span>) ts, _ := strconv.ParseInt(timestamp, <span class="text-amber-400">10</span>, <span class="text-amber-400">64</span>)
<span class="text-pink-400">if</span> math.Abs(<span class="text-teal-400">float64</span>(time.Now().Unix()-ts)) > tolerance { <span class="text-pink-400">if</span> math.Abs(<span class="text-teal-400">float64</span>(time.Now().Unix()-ts)) > tolerance {
http.Error(w, <span class="text-green-400">"Webhook timestamp expired"</span>, <span class="text-amber-400">401</span>) http.Error(w, <span class="text-green-400">"Webhook timestamp expired"</span>, <span class="text-amber-400">401</span>)
<span class="text-pink-400">return</span> <span class="text-pink-400">return</span>
} }
<span class="text-slate-500">// Compute expected signature</span> <span class="text-zinc-500">// Compute expected signature</span>
signedPayload := timestamp + <span class="text-green-400">"."</span> + <span class="text-teal-400">string</span>(payload) signedPayload := timestamp + <span class="text-green-400">"."</span> + <span class="text-teal-400">string</span>(payload)
mac := hmac.New(sha256.New, []<span class="text-teal-400">byte</span>(secret)) mac := hmac.New(sha256.New, []<span class="text-teal-400">byte</span>(secret))
mac.Write([]<span class="text-teal-400">byte</span>(signedPayload)) mac.Write([]<span class="text-teal-400">byte</span>(signedPayload))
expectedSignature := hex.EncodeToString(mac.Sum(<span class="text-pink-400">nil</span>)) expectedSignature := hex.EncodeToString(mac.Sum(<span class="text-pink-400">nil</span>))
<span class="text-slate-500">// Verify signature (timing-safe comparison)</span> <span class="text-zinc-500">// Verify signature (timing-safe comparison)</span>
<span class="text-pink-400">if</span> subtle.ConstantTimeCompare( <span class="text-pink-400">if</span> subtle.ConstantTimeCompare(
[]<span class="text-teal-400">byte</span>(expectedSignature), []<span class="text-teal-400">byte</span>(expectedSignature),
[]<span class="text-teal-400">byte</span>(signature), []<span class="text-teal-400">byte</span>(signature),
@ -474,7 +474,7 @@ post <span class="text-green-400">'/webhook'</span> <span class="text-pink-400">
<span class="text-pink-400">return</span> <span class="text-pink-400">return</span>
} }
<span class="text-slate-500">// Signature valid - process the webhook</span> <span class="text-zinc-500">// Signature valid - process the webhook</span>
processWebhook(payload) processWebhook(payload)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
}</code></pre> }</code></pre>
@ -483,53 +483,53 @@ post <span class="text-green-400">'/webhook'</span> <span class="text-pink-400">
{{-- Retry Policy --}} {{-- Retry Policy --}}
<section id="retry-policy" data-scrollspy-target class="mb-12"> <section id="retry-policy" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Retry Policy</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Retry Policy</h2>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
If your endpoint returns a non-2xx status code or times out, we'll retry with exponential backoff: If your endpoint returns a non-2xx status code or times out, we'll retry with exponential backoff:
</p> </p>
<div class="overflow-x-auto mb-4"> <div class="overflow-x-auto mb-4">
<table class="w-full text-sm"> <table class="w-full text-sm">
<thead> <thead>
<tr class="border-b border-slate-200 dark:border-slate-700"> <tr class="border-b border-zinc-200 dark:border-zinc-700">
<th class="text-left py-3 px-4 font-medium text-slate-800 dark:text-slate-200">Attempt</th> <th class="text-left py-3 px-4 font-medium text-zinc-800 dark:text-zinc-200">Attempt</th>
<th class="text-left py-3 px-4 font-medium text-slate-800 dark:text-slate-200">Delay</th> <th class="text-left py-3 px-4 font-medium text-zinc-800 dark:text-zinc-200">Delay</th>
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-slate-200 dark:divide-slate-700"> <tbody class="divide-y divide-zinc-200 dark:divide-zinc-700">
<tr> <tr>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">1 (initial)</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">1 (initial)</td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">Immediate</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">Immediate</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">2</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">2</td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">1 minute</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">1 minute</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">3</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">3</td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">5 minutes</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">5 minutes</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">4</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">4</td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">30 minutes</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">30 minutes</td>
</tr> </tr>
<tr> <tr>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">5 (final)</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">5 (final)</td>
<td class="py-3 px-4 text-slate-600 dark:text-slate-400">2 hours</td> <td class="py-3 px-4 text-zinc-600 dark:text-zinc-400">2 hours</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<p class="text-slate-600 dark:text-slate-400"> <p class="text-zinc-600 dark:text-zinc-400">
After 5 failed attempts, the delivery is marked as failed. If your endpoint fails 10 consecutive deliveries, it will be automatically disabled. You can re-enable it from your webhook settings. After 5 failed attempts, the delivery is marked as failed. If your endpoint fails 10 consecutive deliveries, it will be automatically disabled. You can re-enable it from your webhook settings.
</p> </p>
</section> </section>
{{-- Best Practices --}} {{-- Best Practices --}}
<section id="best-practices" data-scrollspy-target class="mb-12"> <section id="best-practices" data-scrollspy-target class="mb-12">
<h2 class="h3 mb-4 text-slate-800 dark:text-slate-100">Best Practices</h2> <h2 class="h3 mb-4 text-zinc-800 dark:text-zinc-100">Best Practices</h2>
<ul class="space-y-3 text-slate-600 dark:text-slate-400"> <ul class="space-y-3 text-zinc-600 dark:text-zinc-400">
<li class="flex items-start"> <li class="flex items-start">
<svg class="fill-green-500 shrink-0 mr-3 mt-1" width="16" height="16" viewBox="0 0 16 16"> <svg class="fill-green-500 shrink-0 mr-3 mt-1" width="16" height="16" viewBox="0 0 16 16">
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zm3.78 5.22a.75.75 0 0 1 0 1.06l-4.5 4.5a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 1 1 1.06-1.06L6.75 9.19l3.97-3.97a.75.75 0 0 1 1.06 0z"/> <path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zm3.78 5.22a.75.75 0 0 1 0 1.06l-4.5 4.5a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 1 1 1.06-1.06L6.75 9.19l3.97-3.97a.75.75 0 0 1 1.06 0z"/>
@ -570,11 +570,11 @@ post <span class="text-green-400">'/webhook'</span> <span class="text-pink-400">
</section> </section>
{{-- Next steps --}} {{-- Next steps --}}
<div class="flex items-center justify-between pt-8 border-t border-slate-200 dark:border-slate-700"> <div class="flex items-center justify-between pt-8 border-t border-zinc-200 dark:border-zinc-700">
<a href="{{ route('api.guides.qrcodes') }}" class="text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200"> <a href="{{ route('api.guides.qrcodes') }}" class="text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200">
&larr; QR Code Generation &larr; QR Code Generation
</a> </a>
<a href="{{ route('api.guides.errors') }}" class="text-blue-600 hover:text-blue-700 dark:hover:text-blue-500 font-medium"> <a href="{{ route('api.guides.errors') }}" class="text-cyan-600 hover:text-cyan-700 dark:hover:text-cyan-500 font-medium">
Error Handling &rarr; Error Handling &rarr;
</a> </a>
</div> </div>

View file

@ -1,7 +1,7 @@
@extends('api::layouts.docs') @extends('api::layouts.docs')
@section('title', 'API Documentation') @section('title', 'API Documentation')
@section('description', 'Build powerful integrations with the Host UK API. Access biolinks, workspaces, QR codes, and more.') @section('description', 'Build powerful integrations with the API. Access brain memory, content scoring, and more.')
@section('content') @section('content')
<div class="max-w-7xl mx-auto px-4 sm:px-6 py-12 md:py-20"> <div class="max-w-7xl mx-auto px-4 sm:px-6 py-12 md:py-20">
@ -9,18 +9,18 @@
{{-- Hero --}} {{-- Hero --}}
<div class="max-w-3xl mx-auto text-center mb-16"> <div class="max-w-3xl mx-auto text-center mb-16">
<div class="mb-4"> <div class="mb-4">
<span class="font-nycd text-xl text-blue-600">Developer Documentation</span> <span class="font-nycd text-xl text-cyan-600">Developer Documentation</span>
</div> </div>
<h1 class="h1 mb-6 text-slate-800 dark:text-slate-100">Build with the Host UK API</h1> <h1 class="h1 mb-6 text-zinc-800 dark:text-zinc-100">Build with the API</h1>
<p class="text-xl text-slate-600 dark:text-slate-400 mb-8"> <p class="text-xl text-zinc-600 dark:text-zinc-400 mb-8">
Integrate biolinks, workspaces, QR codes, and analytics into your applications. Store and retrieve agent memories, score content for AI patterns,
Full REST API with comprehensive documentation and SDK support. and integrate intelligent tooling into your applications.
</p> </p>
<div class="flex flex-wrap justify-center gap-4"> <div class="flex flex-wrap justify-center gap-4">
<a href="{{ route('api.guides.quickstart') }}" class="btn text-white bg-blue-600 hover:bg-blue-700 px-6 py-3 rounded-sm font-medium"> <a href="{{ route('api.guides.quickstart') }}" class="btn text-white bg-cyan-600 hover:bg-cyan-700 px-6 py-3 rounded-sm font-medium">
Get Started Get Started
</a> </a>
<a href="{{ route('api.reference') }}" class="btn text-slate-600 bg-white border border-slate-200 hover:border-slate-300 dark:text-slate-300 dark:bg-slate-800 dark:border-slate-700 dark:hover:border-slate-600 px-6 py-3 rounded-sm font-medium"> <a href="{{ route('api.reference') }}" class="btn text-zinc-600 bg-white border border-zinc-200 hover:border-zinc-300 dark:text-zinc-300 dark:bg-zinc-800 dark:border-zinc-700 dark:hover:border-zinc-600 px-6 py-3 rounded-sm font-medium">
API Reference API Reference
</a> </a>
</div> </div>
@ -29,50 +29,44 @@
{{-- Features grid --}} {{-- Features grid --}}
<div class="grid md:grid-cols-3 gap-8 mb-16"> <div class="grid md:grid-cols-3 gap-8 mb-16">
{{-- Authentication --}} {{-- Brain Memory --}}
<div class="bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-sm p-6"> <div class="bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-sm p-6">
<div class="w-10 h-10 flex items-center justify-center bg-blue-100 dark:bg-blue-900/30 rounded-sm mb-4"> <div class="w-10 h-10 flex items-center justify-center bg-cyan-100 dark:bg-cyan-900/30 rounded-sm mb-4">
<svg class="w-5 h-5 fill-blue-600" viewBox="0 0 20 20"> <i class="fa-solid fa-brain text-cyan-600"></i>
<path d="M10 2a5 5 0 0 0-5 5v2a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-5a2 2 0 0 0-2-2V7a5 5 0 0 0-5-5zm3 7V7a3 3 0 1 0-6 0v2h6z"/>
</svg>
</div> </div>
<h3 class="h4 mb-2 text-slate-800 dark:text-slate-100">Authentication</h3> <h3 class="h4 mb-2 text-zinc-800 dark:text-zinc-100">Brain Memory</h3>
<p class="text-slate-600 dark:text-slate-400 mb-4"> <p class="text-zinc-600 dark:text-zinc-400 mb-4">
Store and retrieve agent memories with vector search. Powered by Qdrant for semantic retrieval.
</p>
<a href="{{ route('api.reference') }}#brain" class="text-cyan-600 hover:text-cyan-700 dark:hover:text-cyan-500 text-sm font-medium">
Learn more &rarr;
</a>
</div>
{{-- Content Scoring --}}
<div class="bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-sm p-6">
<div class="w-10 h-10 flex items-center justify-center bg-purple-100 dark:bg-purple-900/30 rounded-sm mb-4">
<i class="fa-solid fa-chart-line text-purple-600"></i>
</div>
<h3 class="h4 mb-2 text-zinc-800 dark:text-zinc-100">Content Scoring</h3>
<p class="text-zinc-600 dark:text-zinc-400 mb-4">
Score text for AI-generated patterns and analyse linguistic imprints via the EaaS scoring engine.
</p>
<a href="{{ route('api.reference') }}#score" class="text-cyan-600 hover:text-cyan-700 dark:hover:text-cyan-500 text-sm font-medium">
Learn more &rarr;
</a>
</div>
{{-- Authentication --}}
<div class="bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-sm p-6">
<div class="w-10 h-10 flex items-center justify-center bg-amber-100 dark:bg-amber-900/30 rounded-sm mb-4">
<i class="fa-solid fa-key text-amber-600"></i>
</div>
<h3 class="h4 mb-2 text-zinc-800 dark:text-zinc-100">Authentication</h3>
<p class="text-zinc-600 dark:text-zinc-400 mb-4">
Secure API key authentication with scoped permissions. Generate keys from your workspace settings. Secure API key authentication with scoped permissions. Generate keys from your workspace settings.
</p> </p>
<a href="{{ route('api.guides.authentication') }}" class="text-blue-600 hover:text-blue-700 dark:hover:text-blue-500 text-sm font-medium"> <a href="{{ route('api.guides.authentication') }}" class="text-cyan-600 hover:text-cyan-700 dark:hover:text-cyan-500 text-sm font-medium">
Learn more &rarr;
</a>
</div>
{{-- Biolinks --}}
<div class="bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-sm p-6">
<div class="w-10 h-10 flex items-center justify-center bg-purple-100 dark:bg-purple-900/30 rounded-sm mb-4">
<svg class="w-5 h-5 fill-purple-600" viewBox="0 0 20 20">
<path d="M12.586 4.586a2 2 0 1 1 2.828 2.828l-3 3a2 2 0 0 1-2.828 0 1 1 0 0 0-1.414 1.414 4 4 0 0 0 5.656 0l3-3a4 4 0 0 0-5.656-5.656l-1.5 1.5a1 1 0 1 0 1.414 1.414l1.5-1.5zm-5 5a2 2 0 0 1 2.828 0 1 1 0 1 0 1.414-1.414 4 4 0 0 0-5.656 0l-3 3a4 4 0 1 0 5.656 5.656l1.5-1.5a1 1 0 1 0-1.414-1.414l-1.5 1.5a2 2 0 1 1-2.828-2.828l3-3z"/>
</svg>
</div>
<h3 class="h4 mb-2 text-slate-800 dark:text-slate-100">Biolinks</h3>
<p class="text-slate-600 dark:text-slate-400 mb-4">
Create, update, and manage biolink pages with blocks, themes, and analytics programmatically.
</p>
<a href="{{ route('api.guides.biolinks') }}" class="text-blue-600 hover:text-blue-700 dark:hover:text-blue-500 text-sm font-medium">
Learn more &rarr;
</a>
</div>
{{-- QR Codes --}}
<div class="bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-sm p-6">
<div class="w-10 h-10 flex items-center justify-center bg-teal-100 dark:bg-teal-900/30 rounded-sm mb-4">
<svg class="w-5 h-5 fill-teal-600" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 4a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4zm2 2V5h1v1H5zm-2 7a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-3zm2 2v-1h1v1H5zm7-13a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-3zm1 2v1h1V5h-1z"/>
</svg>
</div>
<h3 class="h4 mb-2 text-slate-800 dark:text-slate-100">QR Codes</h3>
<p class="text-slate-600 dark:text-slate-400 mb-4">
Generate customisable QR codes with colours, logos, and multiple formats for any URL.
</p>
<a href="{{ route('api.guides.qrcodes') }}" class="text-blue-600 hover:text-blue-700 dark:hover:text-blue-500 text-sm font-medium">
Learn more &rarr; Learn more &rarr;
</a> </a>
</div> </div>
@ -81,21 +75,23 @@
{{-- Quick start code example --}} {{-- Quick start code example --}}
<div class="max-w-4xl mx-auto"> <div class="max-w-4xl mx-auto">
<h2 class="h3 mb-6 text-center text-slate-800 dark:text-slate-100">Quick Start</h2> <h2 class="h3 mb-6 text-center text-zinc-800 dark:text-zinc-100">Quick Start</h2>
<div class="bg-slate-800 rounded-sm overflow-hidden"> <div class="bg-zinc-800 rounded-sm overflow-hidden">
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-700"> <div class="flex items-center justify-between px-4 py-2 border-b border-zinc-700">
<span class="text-sm text-slate-400">cURL</span> <span class="text-sm text-zinc-400">cURL</span>
<button class="text-xs text-slate-500 hover:text-slate-300" onclick="navigator.clipboard.writeText(this.closest('.bg-slate-800').querySelector('code').textContent)"> <button class="text-xs text-zinc-500 hover:text-zinc-300" onclick="navigator.clipboard.writeText(this.closest('.bg-zinc-800').querySelector('code').textContent)">
Copy Copy
</button> </button>
</div> </div>
<pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-slate-300"><span class="text-teal-400">curl</span> <span class="text-slate-500">--request</span> GET \ <pre class="overflow-x-auto p-4 text-sm"><code class="font-pt-mono text-zinc-300"><span class="text-teal-400">curl</span> <span class="text-zinc-500">--request</span> POST \
<span class="text-slate-500">--url</span> <span class="text-amber-400">'https://api.host.uk.com/api/v1/bio'</span> \ <span class="text-zinc-500">--url</span> <span class="text-amber-400">'https://api.lthn.ai/v1/brain/recall'</span> \
<span class="text-slate-500">--header</span> <span class="text-amber-400">'Authorization: Bearer hk_your_api_key'</span></code></pre> <span class="text-zinc-500">--header</span> <span class="text-amber-400">'Authorization: Bearer hk_your_api_key'</span> \
<span class="text-zinc-500">--header</span> <span class="text-amber-400">'Content-Type: application/json'</span> \
<span class="text-zinc-500">--data</span> <span class="text-amber-400">'{"query": "hello world"}'</span></code></pre>
</div> </div>
<div class="mt-4 text-center"> <div class="mt-4 text-center">
<a href="{{ route('api.guides.quickstart') }}" class="text-blue-600 hover:text-blue-700 dark:hover:text-blue-500 text-sm font-medium"> <a href="{{ route('api.guides.quickstart') }}" class="text-cyan-600 hover:text-cyan-700 dark:hover:text-cyan-500 text-sm font-medium">
View full quick start guide &rarr; View full quick start guide &rarr;
</a> </a>
</div> </div>
@ -103,30 +99,30 @@
{{-- API endpoints preview --}} {{-- API endpoints preview --}}
<div class="mt-16"> <div class="mt-16">
<h2 class="h3 mb-8 text-center text-slate-800 dark:text-slate-100">API Endpoints</h2> <h2 class="h3 mb-8 text-center text-zinc-800 dark:text-zinc-100">API Endpoints</h2>
<div class="grid md:grid-cols-2 gap-4 max-w-4xl mx-auto"> <div class="grid md:grid-cols-2 gap-4 max-w-4xl mx-auto">
@foreach([ @foreach([
['method' => 'GET', 'path' => '/api/v1/workspaces', 'desc' => 'List all workspaces'], ['method' => 'POST', 'path' => '/v1/brain/remember', 'desc' => 'Store a memory'],
['method' => 'GET', 'path' => '/api/v1/bio', 'desc' => 'List all biolinks'], ['method' => 'POST', 'path' => '/v1/brain/recall', 'desc' => 'Search memories by query'],
['method' => 'POST', 'path' => '/api/v1/bio', 'desc' => 'Create a biolink'], ['method' => 'DELETE', 'path' => '/v1/brain/forget/{id}', 'desc' => 'Delete a memory'],
['method' => 'GET', 'path' => '/api/v1/bio/{id}/qr', 'desc' => 'Generate QR code'], ['method' => 'POST', 'path' => '/v1/score/content', 'desc' => 'Score text for AI patterns'],
['method' => 'GET', 'path' => '/api/v1/shortlinks', 'desc' => 'List short links'], ['method' => 'POST', 'path' => '/v1/score/imprint', 'desc' => 'Linguistic imprint analysis'],
['method' => 'POST', 'path' => '/api/v1/qr/generate', 'desc' => 'Generate QR for any URL'], ['method' => 'GET', 'path' => '/v1/score/health', 'desc' => 'Scoring engine health check'],
] as $endpoint) ] as $endpoint)
<a href="{{ route('api.reference') }}" class="flex items-center gap-4 p-4 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-sm hover:border-blue-300 dark:hover:border-blue-600 transition-colors"> <a href="{{ route('api.reference') }}" class="flex items-center gap-4 p-4 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-sm hover:border-cyan-300 dark:hover:border-cyan-600 transition-colors">
<span class="inline-flex items-center justify-center px-2 py-1 text-xs font-medium rounded {{ $endpoint['method'] === 'GET' ? 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400' : 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400' }}"> <span class="inline-flex items-center justify-center px-2 py-1 text-xs font-medium rounded {{ $endpoint['method'] === 'GET' ? 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900/30 dark:text-cyan-400' : ($endpoint['method'] === 'DELETE' ? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400' : 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400') }}">
{{ $endpoint['method'] }} {{ $endpoint['method'] }}
</span> </span>
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<code class="text-sm font-pt-mono text-slate-800 dark:text-slate-200 truncate block">{{ $endpoint['path'] }}</code> <code class="text-sm font-pt-mono text-zinc-800 dark:text-zinc-200 truncate block">{{ $endpoint['path'] }}</code>
<span class="text-xs text-slate-500 dark:text-slate-400">{{ $endpoint['desc'] }}</span> <span class="text-xs text-zinc-500 dark:text-zinc-400">{{ $endpoint['desc'] }}</span>
</div> </div>
</a> </a>
@endforeach @endforeach
</div> </div>
<div class="mt-8 text-center"> <div class="mt-8 text-center">
<a href="{{ route('api.swagger') }}" class="text-blue-600 hover:text-blue-700 dark:hover:text-blue-500 font-medium"> <a href="{{ route('api.swagger') }}" class="text-cyan-600 hover:text-cyan-700 dark:hover:text-cyan-500 font-medium">
View all endpoints in Swagger UI &rarr; View all endpoints in Swagger UI &rarr;
</a> </a>
</div> </div>

View file

@ -1,19 +1,14 @@
@php
$appName = config('core.app.name', 'Core PHP');
@endphp
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" class="scroll-smooth"> <html lang="en" class="scroll-smooth">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>@yield('title', 'API Documentation') - Host UK</title> <title>@yield('title', 'API Documentation') - {{ $appName }}</title>
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="csrf-token" content="{{ csrf_token() }}">
<meta name="description" content="@yield('description', 'Host UK API documentation, guides, and reference.')"> <meta name="description" content="@yield('description', $appName . ' REST API documentation, guides, and reference.')">
<!-- Respect user's dark mode preference before page renders -->
<script>
if (localStorage.getItem('flux.appearance') === 'dark' ||
(!localStorage.getItem('flux.appearance') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
}
</script>
<!-- Fonts --> <!-- Fonts -->
@include('layouts::partials.fonts') @include('layouts::partials.fonts')
@ -21,22 +16,19 @@
<!-- Font Awesome Pro --> <!-- Font Awesome Pro -->
<link rel="stylesheet" href="{{ \Core\Helpers\Cdn::versioned('vendor/fontawesome/css/all.min.css') }}"> <link rel="stylesheet" href="{{ \Core\Helpers\Cdn::versioned('vendor/fontawesome/css/all.min.css') }}">
<!-- Tailwind / Vite --> <!-- Tailwind / Vite + Flux -->
@vite(['resources/css/app.css', 'resources/js/app.js']) @vite(['resources/css/app.css', 'resources/js/app.js'])
@fluxAppearance
<!-- Alpine.js -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<style>[x-cloak] { display: none !important; }</style>
@stack('head') @stack('head')
</head> </head>
<body class="font-sans antialiased bg-white text-slate-800 dark:bg-slate-900 dark:text-slate-200"> <body class="font-sans antialiased bg-white text-zinc-800 dark:bg-zinc-900 dark:text-zinc-200">
<div class="flex flex-col min-h-screen overflow-hidden"> <div class="flex flex-col min-h-screen overflow-hidden">
{{-- Site header --}} {{-- Site header --}}
<header class="fixed w-full z-30"> <header class="fixed w-full z-30">
<div class="absolute inset-0 bg-white/70 border-b border-slate-200 backdrop-blur-sm -z-10 dark:bg-slate-900/70 dark:border-slate-800" aria-hidden="true"></div> <div class="absolute inset-0 bg-white/70 border-b border-zinc-200 backdrop-blur-sm -z-10 dark:bg-zinc-900/70 dark:border-zinc-800" aria-hidden="true"></div>
<div class="max-w-7xl mx-auto px-4 sm:px-6"> <div class="max-w-7xl mx-auto px-4 sm:px-6">
<div class="flex items-center justify-between h-16 md:h-20"> <div class="flex items-center justify-between h-16 md:h-20">
@ -45,33 +37,33 @@
<div class="flex items-center gap-4 md:gap-8"> <div class="flex items-center gap-4 md:gap-8">
{{-- Logo --}} {{-- Logo --}}
<a href="{{ route('api.docs') }}" class="flex items-center gap-2"> <a href="{{ route('api.docs') }}" class="flex items-center gap-2">
<svg class="w-8 h-8 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <svg class="w-8 h-8 text-cyan-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75 22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3-4.5 16.5" /> <path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75 22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3-4.5 16.5" />
</svg> </svg>
<span class="font-semibold text-slate-800 dark:text-slate-200">Host UK API</span> <span class="font-semibold text-zinc-800 dark:text-zinc-200">{{ $appName }} API</span>
</a> </a>
{{-- Search --}} {{-- Search --}}
<div class="grow" x-data="{ searchOpen: false }"> <div class="grow" x-data="{ searchOpen: false }">
<button <button
class="w-full sm:w-80 text-sm bg-white text-slate-400 inline-flex items-center justify-between leading-5 pl-3 pr-2 py-2 rounded border border-slate-200 hover:border-slate-300 shadow-sm whitespace-nowrap dark:text-slate-500 dark:bg-slate-800 dark:border-slate-700 dark:hover:border-slate-600" class="w-full sm:w-80 text-sm bg-white text-zinc-400 inline-flex items-center justify-between leading-5 pl-3 pr-2 py-2 rounded border border-zinc-200 hover:border-zinc-300 shadow-sm whitespace-nowrap dark:text-zinc-500 dark:bg-zinc-800 dark:border-zinc-700 dark:hover:border-zinc-600"
@click.prevent="searchOpen = true" @click.prevent="searchOpen = true"
@keydown.slash.window="searchOpen = true" @keydown.slash.window="searchOpen = true"
> >
<div class="flex items-center"> <div class="flex items-center">
<i class="fa-solid fa-magnifying-glass w-4 h-4 mr-3 text-slate-400"></i> <i class="fa-solid fa-magnifying-glass w-4 h-4 mr-3 text-zinc-400"></i>
<span>Search<span class="hidden sm:inline"> docs</span>...</span> <span>Search<span class="hidden sm:inline"> docs</span>...</span>
</div> </div>
<kbd class="hidden sm:inline-flex items-center justify-center h-5 w-5 text-xs font-medium text-slate-500 rounded border border-slate-200 dark:bg-slate-700 dark:text-slate-400 dark:border-slate-600">/</kbd> <kbd class="hidden sm:inline-flex items-center justify-center h-5 w-5 text-xs font-medium text-zinc-500 rounded border border-zinc-200 dark:bg-zinc-700 dark:text-zinc-400 dark:border-zinc-600">/</kbd>
</button> </button>
{{-- Search modal placeholder --}} {{-- Search modal placeholder --}}
<template x-teleport="body"> <template x-teleport="body">
<div x-show="searchOpen" x-cloak> <div x-show="searchOpen" x-cloak>
<div class="fixed inset-0 bg-slate-900/20 z-50" @click="searchOpen = false" @keydown.escape.window="searchOpen = false"></div> <div class="fixed inset-0 bg-zinc-900/20 z-50" @click="searchOpen = false" @keydown.escape.window="searchOpen = false"></div>
<div class="fixed inset-0 z-50 overflow-hidden flex items-start top-20 justify-center px-4"> <div class="fixed inset-0 z-50 overflow-hidden flex items-start top-20 justify-center px-4">
<div class="bg-white overflow-auto max-w-2xl w-full max-h-[80vh] rounded-lg shadow-lg dark:bg-slate-800 p-4" @click.outside="searchOpen = false"> <div class="bg-white overflow-auto max-w-2xl w-full max-h-[80vh] rounded-lg shadow-lg dark:bg-zinc-800 p-4" @click.outside="searchOpen = false">
<p class="text-slate-500 dark:text-slate-400 text-center py-8">Search coming soon...</p> <p class="text-zinc-500 dark:text-zinc-400 text-center py-8">Search coming soon...</p>
</div> </div>
</div> </div>
</div> </div>
@ -82,14 +74,14 @@
{{-- Desktop nav --}} {{-- Desktop nav --}}
<nav class="flex items-center gap-6"> <nav class="flex items-center gap-6">
<a href="{{ route('api.guides') }}" class="text-sm {{ request()->routeIs('api.guides*') ? 'font-medium text-blue-600 dark:text-blue-400' : 'text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200' }}">Guides</a> <a href="{{ route('api.guides') }}" class="text-sm {{ request()->routeIs('api.guides*') ? 'font-medium text-cyan-600 dark:text-cyan-400' : 'text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200' }}">Guides</a>
<a href="{{ route('api.reference') }}" class="text-sm {{ request()->routeIs('api.reference') ? 'font-medium text-blue-600 dark:text-blue-400' : 'text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200' }}">API Reference</a> <a href="{{ route('api.reference') }}" class="text-sm {{ request()->routeIs('api.reference') ? 'font-medium text-cyan-600 dark:text-cyan-400' : 'text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200' }}">API Reference</a>
{{-- API Explorer dropdown --}} {{-- API Explorer dropdown --}}
<div class="relative" x-data="{ open: false }" @click.outside="open = false"> <div class="relative" x-data="{ open: false }" @click.outside="open = false">
<button <button
@click="open = !open" @click="open = !open"
class="text-sm flex items-center gap-1 {{ request()->routeIs('api.swagger', 'api.scalar', 'api.redoc') ? 'font-medium text-blue-600 dark:text-blue-400' : 'text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200' }}" class="text-sm flex items-center gap-1 {{ request()->routeIs('api.swagger', 'api.scalar', 'api.redoc') ? 'font-medium text-cyan-600 dark:text-cyan-400' : 'text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200' }}"
> >
API Explorer API Explorer
<svg class="w-4 h-4 transition-transform" :class="{ 'rotate-180': open }" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"> <svg class="w-4 h-4 transition-transform" :class="{ 'rotate-180': open }" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
@ -104,18 +96,18 @@
x-transition:leave="transition ease-in duration-75" x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95" x-transition:leave-end="opacity-0 scale-95"
class="absolute right-0 mt-2 w-40 origin-top-right rounded-lg bg-white shadow-lg ring-1 ring-slate-200 dark:bg-slate-800 dark:ring-slate-700" class="absolute right-0 mt-2 w-40 origin-top-right rounded-lg bg-white shadow-lg ring-1 ring-zinc-200 dark:bg-zinc-800 dark:ring-zinc-700"
x-cloak x-cloak
> >
<div class="py-1"> <div class="py-1">
<a href="{{ route('api.swagger') }}" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700 {{ request()->routeIs('api.swagger') ? 'bg-slate-100 dark:bg-slate-700' : '' }}"> <a href="{{ route('api.swagger') }}" class="block px-4 py-2 text-sm text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-700 {{ request()->routeIs('api.swagger') ? 'bg-zinc-100 dark:bg-zinc-700' : '' }}">
<i class="fa-solid fa-flask w-4 mr-2 text-slate-400"></i>Swagger <i class="fa-solid fa-flask w-4 mr-2 text-zinc-400"></i>Swagger
</a> </a>
<a href="{{ route('api.scalar') }}" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700 {{ request()->routeIs('api.scalar') ? 'bg-slate-100 dark:bg-slate-700' : '' }}"> <a href="{{ route('api.scalar') }}" class="block px-4 py-2 text-sm text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-700 {{ request()->routeIs('api.scalar') ? 'bg-zinc-100 dark:bg-zinc-700' : '' }}">
<i class="fa-solid fa-bolt w-4 mr-2 text-slate-400"></i>Scalar <i class="fa-solid fa-bolt w-4 mr-2 text-zinc-400"></i>Scalar
</a> </a>
<a href="{{ route('api.redoc') }}" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-700 {{ request()->routeIs('api.redoc') ? 'bg-slate-100 dark:bg-slate-700' : '' }}"> <a href="{{ route('api.redoc') }}" class="block px-4 py-2 text-sm text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-700 {{ request()->routeIs('api.redoc') ? 'bg-zinc-100 dark:bg-zinc-700' : '' }}">
<i class="fa-solid fa-book w-4 mr-2 text-slate-400"></i>ReDoc <i class="fa-solid fa-book w-4 mr-2 text-zinc-400"></i>ReDoc
</a> </a>
</div> </div>
</div> </div>
@ -125,7 +117,7 @@
<button <button
x-data="{ dark: document.documentElement.classList.contains('dark') }" x-data="{ dark: document.documentElement.classList.contains('dark') }"
type="button" type="button"
class="p-2 text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200" class="p-2 text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200"
aria-label="Toggle dark mode" aria-label="Toggle dark mode"
x-on:click="dark = !dark; document.documentElement.classList.toggle('dark'); localStorage.setItem('flux.appearance', dark ? 'dark' : 'light')" x-on:click="dark = !dark; document.documentElement.classList.toggle('dark'); localStorage.setItem('flux.appearance', dark ? 'dark' : 'light')"
> >
@ -144,16 +136,15 @@
</main> </main>
{{-- Site footer --}} {{-- Site footer --}}
<footer class="border-t border-slate-200 dark:border-slate-800"> <footer class="border-t border-zinc-200 dark:border-zinc-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6 py-8"> <div class="max-w-7xl mx-auto px-4 sm:px-6 py-8">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4"> <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div class="text-sm text-slate-500 dark:text-slate-400"> <div class="text-sm text-zinc-500 dark:text-zinc-400">
&copy; {{ date('Y') }} Host UK. All rights reserved. &copy; {{ date('Y') }} {{ $appName }}. All rights reserved.
</div> </div>
<div class="flex gap-6"> <div class="flex gap-6">
<a href="https://host.uk.com" class="text-sm text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200">Host UK</a> <a href="{{ route('api.openapi.json') }}" class="text-sm text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200">OpenAPI Spec</a>
<a href="{{ route('api.openapi.json') }}" class="text-sm text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200">OpenAPI Spec</a> <a href="{{ str_replace('api.', 'mcp.', request()->getSchemeAndHttpHost()) }}" class="text-sm text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200">MCP Portal</a>
<a href="{{ str_replace('api.', 'mcp.', request()->getSchemeAndHttpHost()) }}" class="text-sm text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200">MCP Portal</a>
</div> </div>
</div> </div>
</div> </div>
@ -161,6 +152,7 @@
</div> </div>
@fluxScripts
@stack('scripts') @stack('scripts')
</body> </body>
</html> </html>

View file

@ -1,8 +1,8 @@
@props(['method', 'path', 'description', 'body' => null, 'response']) @props(['method', 'path', 'description', 'body' => null, 'response'])
<div class="mb-8 border border-slate-200 dark:border-slate-700 rounded-sm overflow-hidden"> <div class="mb-8 border border-zinc-200 dark:border-zinc-700 rounded-sm overflow-hidden">
{{-- Header --}} {{-- Header --}}
<div class="flex items-center gap-4 px-4 py-3 bg-slate-50 dark:bg-slate-800/50 border-b border-slate-200 dark:border-slate-700"> <div class="flex items-center gap-4 px-4 py-3 bg-zinc-50 dark:bg-zinc-800/50 border-b border-zinc-200 dark:border-zinc-700">
<span class="inline-flex items-center justify-center px-2 py-1 text-xs font-semibold rounded <span class="inline-flex items-center justify-center px-2 py-1 text-xs font-semibold rounded
@if($method === 'GET') bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 @if($method === 'GET') bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400
@elseif($method === 'POST') bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 @elseif($method === 'POST') bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400
@ -11,26 +11,26 @@
@endif"> @endif">
{{ $method }} {{ $method }}
</span> </span>
<code class="text-sm font-pt-mono text-slate-800 dark:text-slate-200">{{ $path }}</code> <code class="text-sm font-pt-mono text-zinc-800 dark:text-zinc-200">{{ $path }}</code>
</div> </div>
{{-- Body --}} {{-- Body --}}
<div class="p-4"> <div class="p-4">
<p class="text-slate-600 dark:text-slate-400 mb-4">{{ $description }}</p> <p class="text-zinc-600 dark:text-zinc-400 mb-4">{{ $description }}</p>
@if($body) @if($body)
<div class="mb-4"> <div class="mb-4">
<h4 class="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-2">Request Body</h4> <h4 class="text-xs font-semibold text-zinc-500 dark:text-zinc-400 uppercase tracking-wider mb-2">Request Body</h4>
<div class="bg-slate-800 rounded-sm overflow-hidden"> <div class="bg-zinc-800 rounded-sm overflow-hidden">
<pre class="overflow-x-auto p-3 text-sm"><code class="font-pt-mono text-slate-300">{{ $body }}</code></pre> <pre class="overflow-x-auto p-3 text-sm"><code class="font-pt-mono text-zinc-300">{{ $body }}</code></pre>
</div> </div>
</div> </div>
@endif @endif
<div> <div>
<h4 class="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider mb-2">Response</h4> <h4 class="text-xs font-semibold text-zinc-500 dark:text-zinc-400 uppercase tracking-wider mb-2">Response</h4>
<div class="bg-slate-800 rounded-sm overflow-hidden"> <div class="bg-zinc-800 rounded-sm overflow-hidden">
<pre class="overflow-x-auto p-3 text-sm"><code class="font-pt-mono text-slate-300">{{ $response }}</code></pre> <pre class="overflow-x-auto p-3 text-sm"><code class="font-pt-mono text-zinc-300">{{ $response }}</code></pre>
</div> </div>
</div> </div>
</div> </div>

View file

@ -3,8 +3,8 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API Reference - Host UK</title> <title>API Reference - {{ config('core.app.name', 'Core PHP') }}</title>
<meta name="description" content="Host UK API Reference - ReDoc documentation"> <meta name="description" content="{{ config('core.app.name', 'Core PHP') }} API Reference - ReDoc documentation">
<link rel="icon" type="image/png" href="/favicon.png"> <link rel="icon" type="image/png" href="/favicon.png">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style> <style>

View file

@ -6,39 +6,29 @@
<div class="flex"> <div class="flex">
{{-- Sidebar --}} {{-- Sidebar --}}
<aside class="hidden lg:block fixed left-0 top-16 md:top-20 bottom-0 w-64 border-r border-slate-200 dark:border-slate-800"> <aside class="hidden lg:block fixed left-0 top-16 md:top-20 bottom-0 w-64 border-r border-zinc-200 dark:border-zinc-800">
<div class="h-full px-4 py-8 overflow-y-auto no-scrollbar"> <div class="h-full px-4 py-8 overflow-y-auto no-scrollbar">
<nav> <nav>
<h3 class="text-xs font-semibold text-slate-400 uppercase tracking-wider mb-3">Resources</h3> <h3 class="text-xs font-semibold text-zinc-400 uppercase tracking-wider mb-3">Resources</h3>
<ul class="space-y-1"> <ul class="space-y-1">
<li> <li>
<a href="#workspaces" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#brain" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Workspaces Brain Memory
</a> </a>
</li> </li>
<li> <li>
<a href="#biolinks" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#score" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Biolinks Content Scoring
</a> </a>
</li> </li>
<li> <li>
<a href="#blocks" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#collections" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Blocks Collections
</a> </a>
</li> </li>
<li> <li>
<a href="#shortlinks" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full"> <a href="#health" data-scrollspy-link class="block px-3 py-2 text-sm text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Short Links Health
</a>
</li>
<li>
<a href="#qrcodes" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
QR Codes
</a>
</li>
<li>
<a href="#analytics" data-scrollspy-link class="block px-3 py-2 text-sm text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200 rounded-sm relative before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-full">
Analytics
</a> </a>
</li> </li>
</ul> </ul>
@ -50,206 +40,104 @@
<div class="lg:pl-64 w-full"> <div class="lg:pl-64 w-full">
<div class="max-w-4xl mx-auto px-4 sm:px-6 py-12"> <div class="max-w-4xl mx-auto px-4 sm:px-6 py-12">
<h1 class="h1 mb-4 text-slate-800 dark:text-slate-100">API Reference</h1> <h1 class="h1 mb-4 text-zinc-800 dark:text-zinc-100">API Reference</h1>
<p class="text-xl text-slate-600 dark:text-slate-400 mb-4"> <p class="text-xl text-zinc-600 dark:text-zinc-400 mb-4">
Complete reference for all Host UK API endpoints. Complete reference for all API endpoints.
</p> </p>
<p class="text-slate-600 dark:text-slate-400 mb-12"> <p class="text-zinc-600 dark:text-zinc-400 mb-12">
Base URL: <code class="px-2 py-1 bg-slate-100 dark:bg-slate-800 rounded text-sm font-pt-mono">https://api.host.uk.com/api/v1</code> Base URL: <code class="px-2 py-1 bg-zinc-100 dark:bg-zinc-800 rounded text-sm font-pt-mono">https://api.lthn.ai/v1</code>
</p> </p>
{{-- Workspaces --}} {{-- Brain Memory --}}
<section id="workspaces" data-scrollspy-target class="mb-16"> <section id="brain" data-scrollspy-target class="mb-16">
<h2 class="h2 mb-6 text-slate-800 dark:text-slate-100 pb-2 border-b border-slate-200 dark:border-slate-700">Workspaces</h2> <h2 class="h2 mb-6 text-zinc-800 dark:text-zinc-100 pb-2 border-b border-zinc-200 dark:border-zinc-700">Brain Memory</h2>
<p class="text-slate-600 dark:text-slate-400 mb-6"> <p class="text-zinc-600 dark:text-zinc-400 mb-6">
Workspaces are containers for your biolinks, short links, and other resources. Store and retrieve agent memories with vector search. Powered by Qdrant for semantic retrieval.
</p> </p>
@include('api::partials.endpoint', [ @include('api::partials.endpoint', [
'method' => 'GET', 'method' => 'POST',
'path' => '/workspaces', 'path' => '/brain/remember',
'description' => 'List all workspaces you have access to.', 'description' => 'Store a new memory in the vector database.',
'response' => '{"data": [{"id": 1, "name": "My Workspace", "slug": "my-workspace"}]}' 'body' => '{"content": "Go uses structural typing", "type": "fact", "project": "go-agentic", "tags": ["go", "typing"]}',
]) 'response' => '{"id": "mem-abc-123", "type": "fact", "project": "go-agentic", "created_at": "2026-03-03T12:00:00+00:00"}'
@include('api::partials.endpoint', [
'method' => 'GET',
'path' => '/workspaces/current',
'description' => 'Get the current workspace (from API key context).',
'response' => '{"data": {"id": 1, "name": "My Workspace", "slug": "my-workspace"}}'
])
@include('api::partials.endpoint', [
'method' => 'GET',
'path' => '/workspaces/{id}',
'description' => 'Get a specific workspace by ID.',
'response' => '{"data": {"id": 1, "name": "My Workspace", "slug": "my-workspace"}}'
])
</section>
{{-- Biolinks --}}
<section id="biolinks" data-scrollspy-target class="mb-16">
<h2 class="h2 mb-6 text-slate-800 dark:text-slate-100 pb-2 border-b border-slate-200 dark:border-slate-700">Biolinks</h2>
<p class="text-slate-600 dark:text-slate-400 mb-6">
Biolinks are customisable landing pages with blocks of content.
</p>
@include('api::partials.endpoint', [
'method' => 'GET',
'path' => '/bio',
'description' => 'List all biolinks in the workspace.',
'response' => '{"data": [{"id": 1, "url": "mypage", "type": "biolink"}]}'
]) ])
@include('api::partials.endpoint', [ @include('api::partials.endpoint', [
'method' => 'POST', 'method' => 'POST',
'path' => '/bio', 'path' => '/brain/recall',
'description' => 'Create a new biolink.', 'description' => 'Search memories by semantic query. Returns ranked results with confidence scores.',
'body' => '{"url": "mypage", "type": "biolink"}', 'body' => '{"query": "how does typing work in Go", "top_k": 5, "project": "go-agentic"}',
'response' => '{"data": {"id": 1, "url": "mypage", "type": "biolink"}}' 'response' => '{"memories": [{"id": "mem-abc-123", "type": "fact", "content": "Go uses structural typing", "confidence": 0.95}], "scores": {"mem-abc-123": 0.87}}'
])
@include('api::partials.endpoint', [
'method' => 'GET',
'path' => '/bio/{id}',
'description' => 'Get a specific biolink by ID.',
'response' => '{"data": {"id": 1, "url": "mypage", "type": "biolink"}}'
])
@include('api::partials.endpoint', [
'method' => 'PUT',
'path' => '/bio/{id}',
'description' => 'Update a biolink.',
'body' => '{"url": "newpage"}',
'response' => '{"data": {"id": 1, "url": "newpage", "type": "biolink"}}'
]) ])
@include('api::partials.endpoint', [ @include('api::partials.endpoint', [
'method' => 'DELETE', 'method' => 'DELETE',
'path' => '/bio/{id}', 'path' => '/brain/forget/{id}',
'description' => 'Delete a biolink.', 'description' => 'Delete a specific memory by ID.',
'response' => '{"message": "Deleted successfully"}' 'response' => '{"deleted": true}'
]) ])
</section> </section>
{{-- Blocks --}} {{-- Content Scoring --}}
<section id="blocks" data-scrollspy-target class="mb-16"> <section id="score" data-scrollspy-target class="mb-16">
<h2 class="h2 mb-6 text-slate-800 dark:text-slate-100 pb-2 border-b border-slate-200 dark:border-slate-700">Blocks</h2> <h2 class="h2 mb-6 text-zinc-800 dark:text-zinc-100 pb-2 border-b border-zinc-200 dark:border-zinc-700">Content Scoring</h2>
<p class="text-slate-600 dark:text-slate-400 mb-6"> <p class="text-zinc-600 dark:text-zinc-400 mb-6">
Blocks are content elements within a biolink page. Score text for AI patterns and analyse linguistic imprints via the EaaS scoring engine.
</p> </p>
@include('api::partials.endpoint', [ @include('api::partials.endpoint', [
'method' => 'GET', 'method' => 'POST',
'path' => '/bio/{bioId}/blocks', 'path' => '/score/content',
'description' => 'List all blocks for a biolink.', 'description' => 'Score text for AI-generated content patterns. Returns a score (0-1), confidence, and label.',
'response' => '{"data": [{"id": 1, "type": "link", "data": {"title": "My Link"}}]}' 'body' => '{"text": "The text to analyse for AI patterns", "prompt": "Optional scoring prompt"}',
'response' => '{"score": 0.23, "confidence": 0.91, "label": "human"}'
]) ])
@include('api::partials.endpoint', [ @include('api::partials.endpoint', [
'method' => 'POST', 'method' => 'POST',
'path' => '/bio/{bioId}/blocks', 'path' => '/score/imprint',
'description' => 'Add a new block to a biolink.', 'description' => 'Perform linguistic imprint analysis on text. Returns a unique imprint fingerprint.',
'body' => '{"type": "link", "data": {"title": "My Link", "url": "https://example.com"}}', 'body' => '{"text": "The text to analyse for linguistic patterns"}',
'response' => '{"data": {"id": 1, "type": "link", "data": {"title": "My Link"}}}' 'response' => '{"imprint": "abc123def456", "confidence": 0.88}'
])
@include('api::partials.endpoint', [
'method' => 'PUT',
'path' => '/bio/{bioId}/blocks/{id}',
'description' => 'Update a block.',
'body' => '{"data": {"title": "Updated Link"}}',
'response' => '{"data": {"id": 1, "type": "link", "data": {"title": "Updated Link"}}}'
])
@include('api::partials.endpoint', [
'method' => 'DELETE',
'path' => '/bio/{bioId}/blocks/{id}',
'description' => 'Delete a block.',
'response' => '{"message": "Deleted successfully"}'
]) ])
</section> </section>
{{-- Short Links --}} {{-- Collections --}}
<section id="shortlinks" data-scrollspy-target class="mb-16"> <section id="collections" data-scrollspy-target class="mb-16">
<h2 class="h2 mb-6 text-slate-800 dark:text-slate-100 pb-2 border-b border-slate-200 dark:border-slate-700">Short Links</h2> <h2 class="h2 mb-6 text-zinc-800 dark:text-zinc-100 pb-2 border-b border-zinc-200 dark:border-zinc-700">Collections</h2>
<p class="text-slate-600 dark:text-slate-400 mb-6"> <p class="text-zinc-600 dark:text-zinc-400 mb-6">
Short links redirect to any URL with tracking. Manage vector database collections for brain memory storage.
</p> </p>
@include('api::partials.endpoint', [
'method' => 'GET',
'path' => '/shortlinks',
'description' => 'List all short links in the workspace.',
'response' => '{"data": [{"id": 1, "url": "abc123", "destination": "https://example.com"}]}'
])
@include('api::partials.endpoint', [ @include('api::partials.endpoint', [
'method' => 'POST', 'method' => 'POST',
'path' => '/shortlinks', 'path' => '/brain/collections',
'description' => 'Create a new short link.', 'description' => 'Ensure the workspace collection exists in the vector database. Creates it if missing.',
'body' => '{"destination": "https://example.com"}', 'response' => '{"status": "ok"}'
'response' => '{"data": {"id": 1, "url": "abc123", "destination": "https://example.com"}}'
]) ])
</section> </section>
{{-- QR Codes --}} {{-- Health --}}
<section id="qrcodes" data-scrollspy-target class="mb-16"> <section id="health" data-scrollspy-target class="mb-16">
<h2 class="h2 mb-6 text-slate-800 dark:text-slate-100 pb-2 border-b border-slate-200 dark:border-slate-700">QR Codes</h2> <h2 class="h2 mb-6 text-zinc-800 dark:text-zinc-100 pb-2 border-b border-zinc-200 dark:border-zinc-700">Health</h2>
<p class="text-slate-600 dark:text-slate-400 mb-6"> <p class="text-zinc-600 dark:text-zinc-400 mb-6">
Generate customisable QR codes for biolinks or any URL. Health check endpoints. These do not require authentication.
</p> </p>
@include('api::partials.endpoint', [ @include('api::partials.endpoint', [
'method' => 'GET', 'method' => 'GET',
'path' => '/bio/{id}/qr', 'path' => '/score/health',
'description' => 'Get QR code data for a biolink.', 'description' => 'Check the health of the scoring engine and its upstream services.',
'response' => '{"data": {"svg": "<svg>...</svg>", "url": "https://lt.hn/mypage"}}' 'response' => '{"status": "healthy", "upstream_status": 200}'
])
@include('api::partials.endpoint', [
'method' => 'GET',
'path' => '/bio/{id}/qr/download',
'description' => 'Download QR code as PNG/SVG. Query params: format (png|svg), size (100-2000).',
'response' => 'Binary image data'
])
@include('api::partials.endpoint', [
'method' => 'POST',
'path' => '/qr/generate',
'description' => 'Generate QR code for any URL.',
'body' => '{"url": "https://example.com", "format": "svg", "size": 300}',
'response' => '{"data": {"svg": "<svg>...</svg>"}}'
])
@include('api::partials.endpoint', [
'method' => 'GET',
'path' => '/qr/options',
'description' => 'Get available QR code customisation options.',
'response' => '{"data": {"formats": ["png", "svg"], "sizes": {"min": 100, "max": 2000}}}'
])
</section>
{{-- Analytics --}}
<section id="analytics" data-scrollspy-target class="mb-16">
<h2 class="h2 mb-6 text-slate-800 dark:text-slate-100 pb-2 border-b border-slate-200 dark:border-slate-700">Analytics</h2>
<p class="text-slate-600 dark:text-slate-400 mb-6">
View analytics data for your biolinks.
</p>
@include('api::partials.endpoint', [
'method' => 'GET',
'path' => '/bio/{id}/analytics',
'description' => 'Get analytics for a biolink. Query params: period (7d|30d|90d).',
'response' => '{"data": {"views": 1234, "clicks": 567, "unique_visitors": 890}}'
]) ])
</section> </section>
{{-- CTA --}} {{-- CTA --}}
<div class="mt-12 p-6 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-sm text-center"> <div class="mt-12 p-6 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-sm text-center">
<h3 class="h4 mb-2 text-slate-800 dark:text-slate-100">Try it out</h3> <h3 class="h4 mb-2 text-zinc-800 dark:text-zinc-100">Try it out</h3>
<p class="text-slate-600 dark:text-slate-400 mb-4">Test endpoints interactively with Swagger UI.</p> <p class="text-zinc-600 dark:text-zinc-400 mb-4">Test endpoints interactively with Swagger UI.</p>
<a href="{{ route('api.swagger') }}" class="btn text-white bg-blue-600 hover:bg-blue-700 px-6 py-2 rounded-sm font-medium"> <a href="{{ route('api.swagger') }}" class="btn text-white bg-cyan-600 hover:bg-cyan-700 px-6 py-2 rounded-sm font-medium">
Open Swagger UI Open Swagger UI
</a> </a>
</div> </div>

View file

@ -3,8 +3,8 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API Reference - Host UK</title> <title>API Reference - {{ config('core.app.name', 'Core PHP') }}</title>
<meta name="description" content="Host UK API Reference - Interactive documentation with code samples"> <meta name="description" content="{{ config('core.app.name', 'Core PHP') }} API Reference - Interactive documentation with code samples">
<link rel="icon" type="image/png" href="/favicon.png"> <link rel="icon" type="image/png" href="/favicon.png">
<style> <style>
* { box-sizing: border-box; } * { box-sizing: border-box; }
@ -60,8 +60,8 @@
"clientKey": "guzzle" "clientKey": "guzzle"
}, },
"metaData": { "metaData": {
"title": "Host UK API", "title": "{{ config('core.app.name', 'Core PHP') }} API",
"description": "API documentation for Host UK services" "description": "API documentation for {{ config('core.app.name', 'Core PHP') }}"
} }
}' }'
></script> ></script>

View file

@ -20,13 +20,13 @@
@section('content') @section('content')
<div class="max-w-7xl mx-auto px-4 sm:px-6 py-8"> <div class="max-w-7xl mx-auto px-4 sm:px-6 py-8">
<div class="mb-8"> <div class="mb-8">
<h1 class="h2 mb-2 text-slate-800 dark:text-slate-100">Swagger UI</h1> <h1 class="h2 mb-2 text-zinc-800 dark:text-zinc-100">Swagger UI</h1>
<p class="text-slate-600 dark:text-slate-400"> <p class="text-zinc-600 dark:text-zinc-400">
Interactive API explorer. Try out endpoints directly from your browser. Interactive API explorer. Try out endpoints directly from your browser.
</p> </p>
</div> </div>
<div id="swagger-ui" class="bg-white dark:bg-slate-800 rounded-sm border border-slate-200 dark:border-slate-700 p-4"></div> <div id="swagger-ui" class="bg-white dark:bg-zinc-800 rounded-sm border border-zinc-200 dark:border-zinc-700 p-4"></div>
</div> </div>
@endsection @endsection