Skip to content

API Conventions

This page covers the conventions that apply to every endpoint in the oHallo API. Read this before making your first API call.

All API requests use the following base URL:

https://api.ohallo.eu

All endpoints are prefixed with /api/. For example, the conversations list endpoint is:

https://api.ohallo.eu/api/conversations

All request and response bodies use JSON. Set the Content-Type header on requests that include a body:

Terminal window
curl https://api.ohallo.eu/api/workspaces/ws_abc123/kb-entries \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx" \
-H "Content-Type: application/json" \
-d '{"question": "What are your shipping rates?", "answer": "Standard shipping is free for orders over EUR 100.", "topics": ["shipping"]}'

Every request must include an Authorization header with a Bearer token:

Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx

See the Authentication page for details on API key creation, scopes, and security practices.

Most endpoints require a workspaceId parameter to scope the request. This is passed either as a path parameter or a query parameter, depending on the endpoint:

Terminal window
# Path parameter
GET /api/workspaces/{workspaceId}/kb-entries
# Query parameter
GET /api/conversations?workspaceId=ws_a1b2c3d4-e5f6-7890-abcd-ef1234567890

If your API key is scoped to a specific workspace, you still need to provide the workspaceId parameter — the API validates that it matches.

Larger list endpoints (e.g. GET /api/conversations) use cursor-based pagination:

ParameterTypeDefaultDescription
limitinteger50Page size (max 100).
cursorstringOpaque cursor returned as nextCursor on the previous response. Omit on the first call.

Cursor-paginated response format:

{
"data": [
{ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "open" }
],
"nextCursor": "eyJsYXN0SWQiOiJhMWIyYzNkNCJ9"
}

When the response omits nextCursor, you have reached the end of the result set. Cursors are opaque and may change format without warning — pass them back as-is, do not parse or assume their shape.

Smaller list endpoints (e.g. /contacts, /accounts, /kb-entries/search, /policy-entries/search, /attention-items) return a bare JSON array — pass the relevant filter / search params to narrow results rather than paginating. The full-page array shape is documented per-endpoint.

Example — fetching the next page after the first 50 conversations:

Terminal window
curl "https://api.ohallo.eu/api/conversations?workspaceId=a1b2c3d4-e5f6-7890-abcd-ef1234567890&limit=50&cursor=eyJsYXN0SWQiOiJhMWIyYzNkNCJ9" \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"

All timestamps are in ISO 8601 format, in UTC:

{
"createdAt": "2026-03-15T14:30:00.000Z",
"updatedAt": "2026-03-15T16:45:12.000Z"
}

All resource identifiers are UUID v4 strings:

{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"workspaceId": "f0e1d2c3-b4a5-6789-0abc-def123456789"
}

All errors follow a consistent shape:

{
"error": "A human-readable error message"
}

Validation errors (422) include structured details:

{
"error": "Invalid request",
"details": {
"fieldErrors": {
"question": ["Required"],
"topics": ["Expected array, received string"]
},
"formErrors": []
}
}
CodeMeaning
200Success
201Resource created
400Bad request — malformed JSON or missing required fields
401Unauthorized — missing or invalid API key
403Forbidden — valid key but insufficient scopes
404Resource not found
422Unprocessable entity — validation failed on request body
429Rate limit exceeded
500Internal server error

API requests are rate-limited to 300 requests per minute per API key. Rate limit information is included in response headers:

HeaderDescription
RateLimit-LimitMaximum requests allowed per window (300)
RateLimit-RemainingRequests remaining in the current window
RateLimit-ResetSeconds until the rate limit window resets

When the limit is exceeded, the API returns 429 Too Many Requests:

{
"error": "Rate limit exceeded. Retry after 12 seconds."
}

Handle rate limits in your integration:

async function fetchWithRetry(url: string, options: RequestInit): Promise<Response> {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("RateLimit-Reset") ?? "10", 10);
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
return fetchWithRetry(url, options);
}
return response;
}

You can include an x-trace-id header on any request to correlate it with your own logging:

Terminal window
curl https://api.ohallo.eu/api/conversations \
-H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx" \
-H "x-trace-id: my-system-request-12345"

The trace ID is propagated through all internal services and appears in support logs. This is useful when debugging issues with the oHallo team.