ZENIA provides a customer-facing REST API under /api/v1 for server-to-server integrations where API Access is enabled for the workspace. Use it to read workspace data, inspect domains, fetch usage, and send chat messages without relying on the browser dashboard.
API access is protected by scoped API keys created inside Account > API Access. Each key is tied to a workspace and to the access rules available for the current plan.
What this means if you are not technical
API Access lets your own systems talk to ZENIA without opening the dashboard manually. In simple terms, you create a private key, give it only the permissions it needs, and let your website, CRM, internal app, or automation tool request information from ZENIA.
A typical setup looks like this: create a key, store it on your server, choose an indexed domain that already belongs to your workspace, and let your backend call the API to read workspace information, inspect domains, check usage, or send chat questions.
If you are working with a developer, your part is usually to create the key, copy it once, share the indexed domain name, and confirm which actions the integration should be allowed to perform.
Who can use API Access
API Access is available on plans that include the API Access feature. ZENIA checks plan availability and workspace eligibility on every API request.
If the workspace is on a plan without API Access, or if API Access is not currently available for that workspace, API calls are rejected even if a key still exists.
Create, store, and revoke keys safely
Create keys from Account > API Access. You choose a name and one or more scopes for each key.
The raw secret is shown exactly once. ZENIA stores only a secure hash, so you cannot reveal an existing key later. If a secret is lost or exposed, revoke the key and create a new one.
Use separate API keys for separate integrations or environments. The API Usage section can help you see which keys are active and whether they are producing errors or rate-limit responses.
Viewing your API usage
You can monitor your workspace API activity from Account > API Access > API Usage, where this feature is available.
This view helps you confirm whether your integrations are active and healthy. It can show aggregate request counts, successful requests, error requests, rate-limited requests, chat requests, endpoint usage, top error codes, and per-key usage.
The API Usage view is read-only and scoped to your own workspace. It does not show raw API keys, Authorization headers, request bodies, chat messages, assistant replies, IP addresses, or data from other workspaces.
Use it to check:
- whether an integration is still sending requests
- which API key is being used
- whether requests are failing
- whether requests are being rate-limited
- whether chat API usage is increasing
Available scopes
Scopes restrict what each key can do. Use the smallest set of scopes required by your integration.
Scopes are enforced by the API key, while the current plan still decides whether API Access and analytics are available.
| Scope | What it allows | Needed for |
|---|---|---|
chat:write | Send messages to the chat API | POST /api/v1/chat/messages |
workspace:read | Read workspace-level information | GET /api/v1/workspace |
domains:read | Read domains owned by the workspace | GET /api/v1/domains, GET /api/v1/domains/:hostname |
usage:read | Read current usage summaries | GET /api/v1/usage/current, GET /api/v1/domains/:hostname/usage/current |
analytics:read | Read usage history endpoints when analytics is enabled by the live plan | GET /api/v1/usage/history, GET /api/v1/domains/:hostname/usage/history |
Prepare the three values you need
Before making requests, prepare these values:
ApiBaseUrl: the full API base URL, for examplehttps://buildbot.ro/api/v1.ZENIA_API_KEY: the raw API key shown once when you create it.your-indexed-domain.com: a hostname that already exists in the same workspace as the key.
The examples below assume ApiBaseUrl already includes /api/v1, so each request only appends the endpoint path such as /workspace or /chat/messages.
Domain-specific routes require the hostname to exist in your workspace. Chat requests work best after the domain has been indexed, because answers depend on the available knowledge base.
In PowerShell, prefer building JSON with a hashtable and ConvertTo-Json -Compress instead of manually escaping JSON strings.
Authenticate with a Bearer token
Send the raw API key in the Authorization header as Bearer YOUR_KEY.
Use API keys only from your backend or other trusted server environments. Do not use them in browser code. Authentication failures may be rate-limited before request processing continues.
$ApiBaseUrl = "https://buildbot.ro/api/v1"
$ApiKey = "ZENIA_API_KEY"
$Headers = @{
Authorization = "Bearer $ApiKey"
"Content-Type" = "application/json"
}
Invoke-RestMethod -Method Get -Uri "$ApiBaseUrl/workspace" -Headers $HeadersAvailable endpoints
All endpoints are mounted under /api/v1. Domain endpoints only return domains owned by the authenticated workspace.
Read and chat endpoints may be rate-limited to protect service quality. If you exceed the limit, the API returns RATE_LIMITED and may include a Retry-After header.
| Endpoint | Required scope | Purpose |
|---|---|---|
GET / | Any valid key | API status and capability check |
GET /workspace | workspace:read | Workspace plan and access context |
GET /domains | domains:read | List workspace domains |
GET /domains/:hostname | domains:read | Read one owned domain |
GET /usage/current | usage:read | Current workspace usage |
GET /usage/history | analytics:read | Workspace usage history |
GET /domains/:hostname/usage/current | usage:read | Current usage for one domain |
GET /domains/:hostname/usage/history | analytics:read | Historical usage for one domain |
POST /chat/messages | chat:write | Send a chat message and receive the grounded answer payload |
Send messages from your backend
The chat endpoint accepts a domain hostname and the user message. ZENIA applies the domain's server-side retrieval and assistant settings automatically.
Domain-specific chat works best after the domain has been indexed, because the answer depends on the available knowledge base. The first chat request can omit sessionId. When supported, ZENIA generates one automatically and returns it in the response.
What a successful chat response looks like
A successful chat request returns ok: true and a message object.
citations may be empty, confidence may be null, and fallback may be omitted when it does not apply.
{"ok":true,"message":{"reply":"Answer text...","sessionId":"generated-or-existing-session-id","citations":[],"confidence":"medium","fallback":false}}Reuse sessionId to continue the same conversation
Your first chat request can omit sessionId. When conversation sessions are supported for the request, the API returns a session identifier in the response.
Store that value and send it again on the next message if you want the follow-up to stay in the same conversation thread.
Code examples
Run these examples only from your backend or a trusted server environment. Do not expose API keys in browser code, mobile apps, public repositories, screenshots, logs, support chats, or AI assistants.
$ApiBaseUrl = "https://buildbot.ro/api/v1"
$ApiKey = "ZENIA_API_KEY"
$Domain = "your-indexed-domain.com"
$Headers = @{
Authorization = "Bearer $ApiKey"
"Content-Type" = "application/json"
}
$Body = @{
domain = $Domain
message = "What services do you offer?"
} | ConvertTo-Json -Compress
Invoke-RestMethod `
-Method Post `
-Uri "$ApiBaseUrl/chat/messages" `
-Headers $Headers `
-Body $Body$Body = @{
domain = $Domain
sessionId = "SESSION_ID_FROM_PREVIOUS_RESPONSE"
message = "And what are the pricing options?"
} | ConvertTo-Json -Compress
Invoke-RestMethod `
-Method Post `
-Uri "$ApiBaseUrl/chat/messages" `
-Headers $Headers `
-Body $BodyInvoke-RestMethod `
-Method Get `
-Uri "$ApiBaseUrl/usage/current" `
-Headers $HeadersHow to test the integration explicitly
Use this rollout order:
- Test the root and workspace endpoints first.
- Test the domain and usage endpoints next.
- Test chat without
sessionIdso you can confirm whether the server generates one. - Test chat with an explicit
sessionIdif your app needs to keep a conversation thread stable. - Run negative tests for missing auth, invalid key, validation failures, oversized payloads, rate limits, and a foreign or missing domain.
After sending a few requests, open Account > API Access > API Usage and confirm whether the counters update.
Once the quick-start request works, run the full smoke test below to check the wider endpoint surface.
Advanced: full endpoint smoke test
Use this script after the quick-start request works. It checks the main read routes, chat routes, and common error cases.
$ApiBaseUrl = "https://buildbot.ro/api/v1"
$ApiKey = "ZENIA_API_KEY"
$Domain = "your-indexed-domain.com"
$Headers = @{
Authorization = "Bearer $ApiKey"
"Content-Type" = "application/json"
}
function Test-Route {
param(
[string]$Name,
[string]$Method,
[string]$Uri,
[object]$Body = $null,
[int[]]$ExpectedStatus = @(200),
[hashtable]$RequestHeaders = $Headers
)
Write-Host ""
Write-Host "=== $Name ===" -ForegroundColor Cyan
Write-Host "$Method $Uri"
try {
$params = @{
Method = $Method
Uri = $Uri
}
if ($null -ne $RequestHeaders) {
$params.Headers = $RequestHeaders
}
if ($null -ne $Body) {
if ($Body -is [string]) {
$params.Body = $Body
} else {
$params.Body = ($Body | ConvertTo-Json -Compress)
}
}
$response = Invoke-RestMethod @params
Write-Host "PASS 200" -ForegroundColor Green
$response | ConvertTo-Json -Depth 10
}
catch {
$status = $null
$text = $_.Exception.Message
if ($_.Exception.Response) {
$status = [int]$_.Exception.Response.StatusCode
$reader = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream())
$text = $reader.ReadToEnd()
}
if ($ExpectedStatus -contains $status) {
Write-Host "PASS $status" -ForegroundColor Green
} else {
Write-Host "FAIL status=$status expected=$($ExpectedStatus -join ",")" -ForegroundColor Red
}
Write-Host $text
}
}
Test-Route -Name "GET /api/v1" -Method "GET" -Uri "$ApiBaseUrl"
Test-Route -Name "GET /api/v1/workspace" -Method "GET" -Uri "$ApiBaseUrl/workspace"
Test-Route -Name "GET /api/v1/domains" -Method "GET" -Uri "$ApiBaseUrl/domains"
Test-Route -Name "GET /api/v1/domains/:hostname" -Method "GET" -Uri "$ApiBaseUrl/domains/$Domain"
Test-Route -Name "GET /api/v1/usage/current" -Method "GET" -Uri "$ApiBaseUrl/usage/current"
Test-Route -Name "GET /api/v1/usage/history" -Method "GET" -Uri "$ApiBaseUrl/usage/history?months=12"
Test-Route -Name "GET /api/v1/domains/:hostname/usage/current" -Method "GET" -Uri "$ApiBaseUrl/domains/$Domain/usage/current"
Test-Route -Name "GET /api/v1/domains/:hostname/usage/history" -Method "GET" -Uri "$ApiBaseUrl/domains/$Domain/usage/history?months=12"
$chat1 = @{
domain = $Domain
message = "What services do you offer?"
}
Test-Route -Name "POST /api/v1/chat/messages without sessionId" -Method "POST" -Uri "$ApiBaseUrl/chat/messages" -Body $chat1
$chat2 = @{
domain = $Domain
sessionId = "manual-test-session-001"
message = "And what are the pricing options?"
}
Test-Route -Name "POST /api/v1/chat/messages with sessionId" -Method "POST" -Uri "$ApiBaseUrl/chat/messages" -Body $chat2
$BadHeaders = @{
Authorization = "Bearer invalid_key"
"Content-Type" = "application/json"
}
Test-Route -Name "GET /api/v1/workspace without auth" -Method "GET" -Uri "$ApiBaseUrl/workspace" -RequestHeaders $null -ExpectedStatus @(401)
Test-Route -Name "GET /api/v1/workspace with invalid key" -Method "GET" -Uri "$ApiBaseUrl/workspace" -RequestHeaders $BadHeaders -ExpectedStatus @(401)
Test-Route -Name "POST /api/v1/chat/messages with empty body" -Method "POST" -Uri "$ApiBaseUrl/chat/messages" -Body "{}" -ExpectedStatus @(400)
Test-Route -Name "POST /api/v1/chat/messages with malformed JSON" -Method "POST" -Uri "$ApiBaseUrl/chat/messages" -Body '{' -ExpectedStatus @(400)
$largeBody = @{
domain = $Domain
message = ("x" * 40000)
} | ConvertTo-Json -Compress
Test-Route -Name "POST /api/v1/chat/messages with oversized body" -Method "POST" -Uri "$ApiBaseUrl/chat/messages" -Body $largeBody -ExpectedStatus @(413)
Test-Route -Name "POST /api/v1/chat/messages with foreign domain" -Method "POST" -Uri "$ApiBaseUrl/chat/messages" -Body @{ domain = "not-owned-example.test"; message = "Hello" } -ExpectedStatus @(404)Common error responses
Error bodies include a machine-readable code field. Use that field in your integration. Do not rely on the exact wording of the human-readable error message.
When the API returns 429 RATE_LIMITED, respect the Retry-After header when it is present.
| Code | Meaning | What to do |
|---|---|---|
API_AUTH_REQUIRED | Missing or malformed Bearer token | Send Authorization: Bearer <key> |
API_SCOPE_REQUIRED | Key does not have the required scope | Create a key with the required scope |
API_ACCESS_NOT_AVAILABLE | Current plan does not include API Access, or API Access is not available for the workspace | Use a plan and workspace setup that includes API Access |
DOMAIN_NOT_FOUND | Domain is missing, deleted, or not owned by the workspace | Check the domain in the dashboard |
API_KEY_INVALID | Key is wrong, revoked, expired, or malformed | Check the key or create a new one |
ANALYTICS_NOT_AVAILABLE | Current plan does not include analytics, or analytics is not available for the workspace | Use current usage endpoints or change to a plan that includes analytics |
VALIDATION_ERROR | Required body fields are missing or invalid | Check required fields such as domain and message |
INVALID_JSON | Request body is not valid JSON | Fix JSON formatting or use ConvertTo-Json |
PAYLOAD_TOO_LARGE | Request body is too large | Reduce the request body size |
RATE_LIMITED | Too many requests | Wait and retry after Retry-After if it is present |
CHAT_RELAY_FAILED | Chat service could not process the message | Retry later; if the problem persists, check service status |
Security best practices
Treat API keys like passwords. Do not paste them into AI assistants, support chats, screenshots, logs, browser code, mobile apps, or public repositories. If a key is exposed, revoke it immediately and create a new one.
Use separate keys for separate integrations or environments. Give each key only the scopes it needs. The dashboard may show when a key was last used, but it does not expose IP addresses.
API Usage is not a request log. It shows aggregate counters only. It does not reveal request bodies, chat messages, assistant replies, Authorization headers, raw API keys, or IP addresses.
