Huck has three authentication boundaries: your server uses a scoped service key, an end user uses a short-lived chat session, and your action receiver signs callbacks with its action secret. These credentials are not interchangeable. A console sign-in never substitutes for a service key.
The API base is https://huck.chat/api/v1. Use HTTPS directly. Service keys belong in your backend environment or secret manager, never HTML, browser storage, source control, analytics, or query strings.
Create the right key
Report an issue with this sectionOpen Developer setup → Create a scoped server key. Use separate keys for ingestion, session issuance, deployment automation, and operational monitoring. A secret is shown once. New tokens use Laravel Sanctum's id|huck_… format; treat the complete value as opaque. Existing service keys issued before the Sanctum migration keep working until their original expiration or revocation.
| Integration | Required scopes |
|---|---|
| Send, delete, retry, or batch records | knowledge:write |
| Read records and reconcile their manifest | knowledge:read |
| Synchronize identities and issue chat sessions | subjects:read, subjects:write, sessions:create |
| Revoke a specific chat session from your backend | sessions:revoke |
| Read/test access rules and readable labels | access:read |
| Save access drafts or labels | access:write |
| Publish access changes | access:publish |
| Read/save prompt versions | prompts:read, prompts:write |
| Publish an evaluated prompt | prompts:publish |
| Read results, save goldens, request evaluations | evaluations:read, evaluations:write, evaluations:run respectively |
| Read/create action definitions | actions:read, actions:write |
| Read/save/publish appearance | appearance:read, appearance:write, appearance:publish respectively |
| Manage service keys | keys:manage plus every permission being delegated |
| Monitor integration status | operations:read |
| Export/erase an identity's retained data | privacy:export, privacy:delete respectively |
The key determines the workspace. Do not include workspace_id or tenant_id in an API request. Every operation requires its own scope; there is no wildcard scope for customer-created keys.
A first request
Report an issue with this sectionSet the variables in your server environment. The placeholder below must be replaced with a real server key, not a chat session. The domain guides use HUCK_INGEST_KEY, HUCK_RELEASE_KEY, and HUCK_MANAGEMENT_KEY for separately scoped integrations; map those names to the keys you create. HUCK_API_URL is the common application URL.
export HUCK_API_URL='https://huck.chat'
export HUCK_SERVICE_KEY='YOUR_SERVER_KEY'
curl --fail-with-body "$HUCK_API_URL/api/v1/knowledge/manifest?after_id=0" \
-H "Authorization: Bearer $HUCK_SERVICE_KEY" \
-H 'Accept: application/json'
Read requests have no body. Write requests use a JSON object with Content-Type: application/json. Mutation parameters belong in that body, never the URL. Duplicate JSON property names are rejected. The API-wide request limit is 2,000,000 bytes; individual operations have smaller limits.
Laravel server-side example:
// config/services.php
'huck' => [
'url' => env('HUCK_API_URL', 'https://huck.chat'),
'key' => env('HUCK_SERVICE_KEY'),
],
// Your application service. Do not return this client or credential to the browser.
$response = Http::baseUrl(rtrim(config('services.huck.url'), '/').'/api/v1/')
->withToken(config('services.huck.key'))
->withOptions(['allow_redirects' => false])
->acceptJson()->connectTimeout(5)->timeout(20)
->get('knowledge/manifest', ['after_id' => 0]);
$response->throw();
$manifest = $response->json();
Native Node.js server-side example; no Huck SDK is required:
export async function huck(path, {method = 'GET', body, idempotencyKey} = {}) {
const headers = {Authorization: `Bearer ${process.env.HUCK_SERVICE_KEY}`,
Accept: 'application/json'};
if (body !== undefined) headers['Content-Type'] = 'application/json';
if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;
const response = await fetch(`${process.env.HUCK_API_URL}/api/v1/${path}`, {
method, headers, redirect: 'error', signal: AbortSignal.timeout(20000),
body: body === undefined ? undefined : JSON.stringify(body),
});
const payload = response.status === 204 ? null : await response.json();
if (!response.ok) {
throw Object.assign(new Error(payload?.detail ?? 'Huck request failed'), {
status: response.status, requestId: response.headers.get('X-Request-ID'),
problem: payload, retryAfter: response.headers.get('Retry-After'),
});
}
return payload;
}
The helper intentionally leaves retry decisions to the workflow. A lost response to a key-creation request must not result in blindly creating another key.
Expiration, permanent keys, and rotation
Report an issue with this sectionKeys expire after 90 days by default. Explicitly set permanent: true to choose no expiration. An explicit expires_at must be in the future and less than one year away. An expired or revoked key cannot be reactivated; create a replacement.
{
"name": "Production ingestion",
"scopes": ["knowledge:read", "knowledge:write"],
"permanent": false,
"expires_at": "2027-01-01T00:00:00Z"
}
Send this to POST /keys with an authorized management key. The example date must still be a future date when used. The response is 201 with key metadata and a one-time secret. GET /keys returns paginated metadata, never secrets. The metadata includes id, version, scopes, expires_at, permanent, revoked_at, and last_used_at.
To change an active key's deadline without replacing its secret, send its current version:
curl --fail-with-body -X PUT "$HUCK_API_URL/api/v1/keys/$KEY_ID/lifetime" \
-H "Authorization: Bearer $HUCK_MANAGEMENT_KEY" \
-H 'Content-Type: application/json' \
--data '{"expected_version":1,"permanent":true}'
For a finite deadline, use permanent: false with expires_at. A 409 means the key changed: reload its metadata and ask your operator to review the current state. A management key cannot grant permissions it lacks, make a child permanent when the management key expires, or extend a child beyond its own lifetime.
POST /keys/{id}/rotate with {} immediately revokes the previous token and returns a new secret. For a gradual rollout, create a second key, install and verify it, then call DELETE /keys/{oldId} with {}. Rotation is immediate, not a grace period. To invalidate all current workspace chat sessions at the same time, use {"revoke_sessions":true}; this also requires sessions:revoke. Revoking only a service key does not implicitly revoke sessions it previously minted.
Successful service API responses include X-Huck-Key-Expires-At (ISO timestamp or never) and X-Huck-Key-Status (active, expiring, permanent, expired, or revoked, depending on the final key state). The control panel shows deadlines and upcoming expiration. Email reminders depend on configured delivery and a verified workspace owner; use your own monitoring as well.
Errors you can act on
Report an issue with this sectionAPI errors use application/problem+json. Store the X-Request-ID / request_id when contacting support; do not include bearer secrets or private source contents.
{
"type": "about:blank",
"title": "Conflict",
"status": 409,
"detail": "The revision is stale or already contains different data.",
"instance": "/api/v1/knowledge",
"message": "The revision is stale or already contains different data.",
"request_id": "example-request-id"
}
| Status | What to do |
|---|---|
400 |
Fix malformed JSON, duplicate fields, invalid header syntax, or misplaced parameters. |
401 |
Check the correct credential type, expiry/revocation, and chat origin. Chat clients obtain a fresh session through their authenticated backend. |
403 |
Check the scope and access rules. Retrying the same authorization does not grant access. |
404 |
Check the resource ID in this workspace; foreign resources are unavailable. |
409 |
Reconcile the current revision, idempotency receipt, lifecycle state, or evaluation fingerprint. Do not blindly overwrite. |
412 |
A subject ETag changed. Reload authoritative permissions and the current ETag. |
413 |
Split the request into smaller records/batches. |
415 |
Use a supported JSON content type. |
422 |
Correct field errors, missing hierarchy ancestors, unsupported schemas, limits, or quota validation. |
428 |
Read the subject first, then send its ETag in If-Match. |
429 |
Honor Retry-After; reduce concurrency and add jitter. |
5xx |
Retain the request ID. Retry only when the operation's documented idempotency contract makes that safe. |
Idempotency is operation-specific. Knowledge accepts a per-record key; evaluation requests and action invocations require their own header. Reuse the same key and identical payload after an uncertain response. Do not assume Idempotency-Key turns every API mutation into an idempotent operation.
Continue with knowledge ingestion, identity and hierarchy access, prompt evaluations, or programmable actions.