# REST API for AI agents and your server

> The HRC pagalba REST API with a personal key: authentication, { error, data } answers, error codes, the OpenAPI file and examples in curl, Node.js and Python.

The operations an AI agent sees over MCP are also available over the REST API. It suits your server, your scripts and AI apps without MCP.

## Authentication

Create an AI agent and its personal key in Settings, AI agents. The key (`hrc_live_...`) is shown once. Send every call with the header:

```text
Authorization: Bearer hrc_live_...
```

The key acts as the member who created the AI agent, and only with the AI agent's permissions.

![The key created dialog](https://hrc.lt/screens/en/agents-key-created.desktop.1600.72d80dd6.webp)

## Calls

Every call is `POST https://api.hrc.lt/private/<object>/<action>` with JSON. Put your company id in every call whose input has `companyId`: `/private/ai-agent-self/get` returns it.

```sh
curl -s https://api.hrc.lt/private/ai-agent-self/get \
  -H "Authorization: Bearer $HRC_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
# {"error":null,"data":{"companyId":"...","companyName":"...",
#   "areas":{...},"tools":[...]}}
```

## Answers and errors

Every answer is `{ error, data }`: one side is always `null`.

```json
{
  "error": {
    "code": "AI_AGENT_NOT_ALLOWED",
    "message": "This AI agent is not allowed to do this. ...",
    "details": { "area": "kb", "needed": "write" }
  },
  "data": null
}
```

| Code                        | Status | Meaning                                                            |
| --------------------------- | ------ | ------------------------------------------------------------------ |
| `NOT_AUTHENTICATED`         | 401    | No key, or the key was revoked or expired.                         |
| `AI_AGENTS_OFF`             | 403    | AI agent access is off for the company.                            |
| `AI_AGENT_PAUSED`           | 403    | The AI agent or the key is paused.                                 |
| `AI_AGENT_NOT_ALLOWED`      | 403    | The area permission is too low (`details.area`, `details.needed`). |
| `AI_AGENT_SEND_NOT_ALLOWED` | 403    | No permission to send to customers.                                |
| `OWNER_ROLE_REQUIRED`       | 403    | The action is for owners only.                                     |
| `COMPANY_NOT_FOUND`         | 404    | Not your company, or the member left it.                           |
| `TOO_MANY_REQUESTS`         | 429    | More than 120 calls a minute (`details.retryAfterSec`).            |
| `VALIDATION_ERROR`          | 400    | Bad input (`details.issues`).                                      |
| `CONFLICT`                  | 409    | Someone changed the object before you: read it again and retry.    |

The `x-language: lt` or `en` header changes the language of error messages.

## The OpenAPI file

Every operation with its input and answer: [https://api.hrc.lt/agent-api/openapi.json](https://api.hrc.lt/agent-api/openapi.json). Load it into Postman, Insomnia or a code generator.

## Node.js

```js
// Node.js 18 or newer
const API = 'https://api.hrc.lt';

async function hrc(path, body) {
  const response = await fetch(API + path, {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + process.env.HRC_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });
  const { error, data } = await response.json();
  if (error) throw new Error(error.code + ': ' + error.message);
  return data;
}

const me = await hrc('/private/ai-agent-self/get', {});
// Views: all, new, open, pending, closed, unassigned, mine, snoozed, spam.
// open is the Active status only; all is every status.
const list = await hrc('/private/conversation/list', {
  companyId: me.companyId,
  view: 'all',
  limit: 20,
});
for (const item of list.items) {
  console.log(item.ref, item.subject, item.contact.name);
}
```

## Python

```python
# Python 3 with requests
import os
import uuid

import requests

API = "https://api.hrc.lt"
HEADERS = {"Authorization": "Bearer " + os.environ["HRC_KEY"]}

def hrc(path, body):
    response = requests.post(
        API + path, json=body, headers=HEADERS, timeout=30
    )
    answer = response.json()
    if answer["error"]:
        error = answer["error"]
        raise RuntimeError(error["code"] + ": " + error["message"])
    return answer["data"]

me = hrc("/private/ai-agent-self/get", {})
company_id = me["companyId"]

# A prepared reply: a team member reviews it and sends it
hrc("/private/prepared-reply/replace", {
    "companyId": company_id,
    "conversationId": "CONVERSATION_ID",
    "bodyMd": "Hello, the payment works again. Thank you for waiting.",
})

# A task linked to the conversation
hrc("/private/task/create", {
    "companyId": company_id,
    "requestId": str(uuid.uuid4()),
    "title": "Payment fails after the bank check",
    "conversationIds": ["CONVERSATION_ID"],
})
```

## Trusted profile fields from your server

Send values that decide something (plan, balance, permissions) from your server. Their source is the team, so neither your site nor the browser can change them. This needs the Conversations and messages area with change access.

```js
// From your backend: values set here are trusted (source "team");
// the site and the browser cannot change them.
// Uses hrc() from the Node.js example.
await hrc('/private/contact/set-profile', {
  companyId: me.companyId,
  contactId: 'CONTACT_ID',
  company: { name: 'Acme', plan: 'Enterprise' },
  set: [
    { key: 'plan', type: 'string', value: 'enterprise' },
    { key: 'seats', type: 'number', value: 40 },
    { key: 'renews_at', type: 'date', value: '2027-01-31' },
  ],
  unset: ['trial_ends_at'],
  addTags: ['key-account'],
});
```

## Limits and retries

120 calls a minute per key. On `429`, wait `details.retryAfterSec` seconds. When you create objects, send a `requestId` (UUID): repeating the same call does not create a duplicate.

Every call goes into the "What the AI agents did" log.
