API Conventions
This page covers the conventions that apply to every endpoint in the oHallo API. Read this before making your first API call.
Base URL
Section titled “Base URL”All API requests use the following base URL:
https://api.ohallo.euAll endpoints are prefixed with /api/. For example, the conversations list endpoint is:
https://api.ohallo.eu/api/conversationsContent type
Section titled “Content type”All request and response bodies use JSON. Set the Content-Type header on requests that include a body:
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"]}'Authentication
Section titled “Authentication”Every request must include an Authorization header with a Bearer token:
Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWxSee the Authentication page for details on API key creation, scopes, and security practices.
Workspace scoping
Section titled “Workspace scoping”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:
# Path parameterGET /api/workspaces/{workspaceId}/kb-entries
# Query parameterGET /api/conversations?workspaceId=ws_a1b2c3d4-e5f6-7890-abcd-ef1234567890If your API key is scoped to a specific workspace, you still need to provide the workspaceId parameter — the API validates that it matches.
Pagination
Section titled “Pagination”Larger list endpoints (e.g. GET /api/conversations) use cursor-based pagination:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Page size (max 100). |
cursor | string | — | Opaque 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:
curl "https://api.ohallo.eu/api/conversations?workspaceId=a1b2c3d4-e5f6-7890-abcd-ef1234567890&limit=50&cursor=eyJsYXN0SWQiOiJhMWIyYzNkNCJ9" \ -H "Authorization: Bearer sf_live_v1_a3Bx9kLmP2qR7wYz4nDfGhJkQpStUvWx"Timestamps
Section titled “Timestamps”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"}Error responses
Section titled “Error responses”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": [] }}HTTP status codes
Section titled “HTTP status codes”| Code | Meaning |
|---|---|
200 | Success |
201 | Resource created |
400 | Bad request — malformed JSON or missing required fields |
401 | Unauthorized — missing or invalid API key |
403 | Forbidden — valid key but insufficient scopes |
404 | Resource not found |
422 | Unprocessable entity — validation failed on request body |
429 | Rate limit exceeded |
500 | Internal server error |
Rate limiting
Section titled “Rate limiting”API requests are rate-limited to 300 requests per minute per API key. Rate limit information is included in response headers:
| Header | Description |
|---|---|
RateLimit-Limit | Maximum requests allowed per window (300) |
RateLimit-Remaining | Requests remaining in the current window |
RateLimit-Reset | Seconds 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;}Request tracing
Section titled “Request tracing”You can include an x-trace-id header on any request to correlate it with your own logging:
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.