Документы
Загрузка, обновление, удаление и экспорт документов — POST /documents, массовое обновление, TTL, идемпотентные повторы и экспорт в NDJSON.
POST /api/v1/indexes/{slug}/documents
POST /api/v1/indexes/{slug}/documents/bulk
GET /api/v1/indexes/{slug}/documents/{externalId}
DELETE /api/v1/indexes/{slug}/documents/{externalId}
DELETE /api/v1/indexes/{slug}/documents?filter_by=…
GET /api/v1/indexes/{slug}/documents/exportЗапись требует documents:write, чтение одного документа — search:read, экспорт — documents:read.
Структура документа
| Поле | Обязательно | Комментарий |
|---|---|---|
external_id | да | 1–256 символов; ваш первичный ключ внутри индекса |
content | да | Индексируемый текст; до SEARCH_MAX_CONTENT_LENGTH (по умолчанию 200 000 символов) |
title | нет | До 1000 символов; входит в query_by по умолчанию |
url | нет | До 2048 символов, хранится без индексации |
source_type | нет | До 64 символов; фасет, удобно разделять api, webhook, connector |
tags | нет | До 50 строк длиной до 64 символов |
metadata | нет | Произвольный JSON, хранится строкой, не ищется |
expires_at | нет | Дата-время ISO 8601; после неё документ удаляется из индекса |
| ваши поля | нет | Любое поле, объявленное в схеме индекса, передаётся как есть |
Пакетный upsert
curl -sS -X POST "$AAC_API/indexes/documentation/documents" \
-H "Authorization: Bearer $AAC_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: import-2026-10-23-01" \
-d '{
"documents": [
{ "external_id": "pricing", "title": "Тарифы", "content": "Pro стоит 29 USD в месяц.", "url": "https://example.com/pricing", "tags": ["billing"] },
{ "external_id": "limits", "title": "Лимиты", "content": "600 запросов в минуту на ключ.", "tags": ["api"], "expires_at": "2026-12-31T23:59:59Z" }
],
"action": "upsert"
}'{
"index": "documentation",
"indexed": 2,
"failed": [],
"usage": {
"plan": "pro",
"period": "2026-10",
"resetsAt": "2026-11-01T00:00:00.000Z",
"searches": { "used": 4120, "limit": 500000 },
"documents": { "used": 139, "limit": 50000 }
},
"request_id": "req_2a6c8e0b4d1f3a5c7e9b1d3f"
}action определяет поведение с существующими документами:
| Действие | Поведение |
|---|---|
upsert (по умолчанию) | Заменить существующий документ, вставить новый |
create | Только вставка; существующий external_id даёт ошибку для этого документа |
update | Частичное обновление — меняются только переданные поля |
emplace | Вставка или слияние без очистки пропущенных полей |
Пакет не является атомарным. Отклонённые документы возвращаются в failed, остальные индексируются:
{
"index": "documentation",
"indexed": 1,
"failed": [{ "external_id": "draft-1", "error": "Document content is required" }],
"usage": { "documents": { "used": 140, "limit": 50000 } },
"request_id": "req_7d1e3b5a9c0f2a4d6b8e0c2a"
}Ограничения: не более 500 документов в запросе и 200 000 символов в content. Превышение даёт 422 INVALID_DOCUMENT с деталями:
{
"error": {
"code": "INVALID_DOCUMENT",
"message": "Too many documents in a single request",
"request_id": "req_4c8a0e2b6d1f3a5c7b9d1e3f",
"details": { "max": 500, "received": 812 }
}
}Идемпотентные повторы
Передайте Idempotency-Key в POST /documents, и повтор той же записи вернёт исходный ответ вместо повторного учёта документов:
curl -sS -i -X POST "$AAC_API/indexes/documentation/documents" \
-H "Authorization: Bearer $AAC_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: import-2026-10-23-01" \
-d '{"documents":[{"external_id":"pricing","content":"Pro стоит 29 USD в месяц."}]}'HTTP/1.1 200 OK
idempotent-replayed: trueЗаписи о повторах живут 10 минут: в Redis (общие для всех инстансов) или в памяти процесса, если Redis не настроен.
Как сопоставляется ключ:
- Ключ действует в пределах индекса: один и тот же ключ на двух индексах — две разные записи.
- Запись хранит отпечаток метода, пути и JSON-тела. Повторное использование ключа для другого тела отклоняется с
422 IDEMPOTENCY_KEY_REUSED, а не возвращает старый ответ, молча теряя новую запись. - Пока первый запрос ещё выполняется, параллельный дубль получает
409 IDEMPOTENCY_IN_FLIGHT; повторите после короткой паузы. Отметка «выполняется» истекает через 120 секунд. - Неудачная запись не сохраняется, поэтому тот же ключ можно сразу использовать снова.
- Повторяется только успешный ответ (
200), байт в байт, включая исходныйrequest_id.
Чтение, обновление, удаление
curl -sS "$AAC_API/indexes/documentation/documents/pricing" \
-H "Authorization: Bearer $AAC_KEY"{
"document": {
"id": "pricing",
"external_id": "pricing",
"title": "Тарифы",
"content": "Pro стоит 29 USD в месяц.",
"url": "https://example.com/pricing",
"source_type": "api",
"organization_id": "org_01h9",
"tags": ["billing"],
"indexed_at": 1761214364
},
"request_id": "req_9e2b4d6a8c0f1e3b5d7a9c1e"
}curl -sS -X DELETE "$AAC_API/indexes/documentation/documents/pricing" \
-H "Authorization: Bearer $AAC_KEY"{
"deleted": true,
"external_id": "pricing",
"request_id": "req_1a3c5e7b9d0f2a4c6e8b0d1f"
}Отсутствующий документ — 404 DOCUMENT_NOT_FOUND:
{
"error": {
"code": "DOCUMENT_NOT_FOUND",
"message": "Document \"pricing\" was not found",
"request_id": "req_6b0d2f4a8c1e3b5d7f9a1c3e"
}
}Массовое обновление и удаление по фильтру
curl -sS -X POST "$AAC_API/indexes/documentation/documents/bulk" \
-H "Authorization: Bearer $AAC_KEY" \
-H "Content-Type: application/json" \
-d '{ "filter_by": "tags:=[billing]", "document": { "category": "pricing" } }'{
"index": "documentation",
"updated": 12,
"filter_by": "tags:=[billing]",
"request_id": "req_3d5f7b9a1c2e4a6b8d0f2a4c"
}curl -sS -X DELETE "$AAC_API/indexes/documentation/documents" \
-H "Authorization: Bearer $AAC_KEY" \
-G --data-urlencode "filter_by=source_type:=connector && indexed_at:<1761214364"{
"index": "documentation",
"deleted": 348,
"filter_by": "source_type:=connector && indexed_at:<1761214364",
"request_id": "req_8f0b2d4a6c8e1b3d5f7a9c1e"
}Для удаления по фильтру filter_by обязателен: без него придёт 400 INVALID_QUERY с сообщением filter_by query parameter is required. Фильтр тенанта подставляется так же, как в поиске.
Экспорт
curl -sS "$AAC_API/indexes/documentation/documents/export" \
-H "Authorization: Bearer $AAC_KEY" \
-o documentation.ndjsonОтвет имеет тип application/x-ndjson и заголовок content-disposition: attachment; filename="documentation-export.ndjson": один JSON-документ на строку — тот же формат, который принимает импорт, поэтому экспорт можно залить в другой индекс.
Документы с истечением срока
expires_at хранится вместе с документом и обрабатывается задачей ретенции, которая удаляет просроченные документы из коллекции. Это самый дешёвый способ убрать из выдачи ограниченный по времени контент (события, релизы, акции) без вызова удаления. Задача выполняется планировщиком; принудительно запустить её можно через POST /admin/jobs/run?job=retention.
Запись из вебхука
Если документы приходят из другой системы, POST /indexes/{slug}/webhook принимает тот же массив документов с HMAC-подписью и допускает до 1000 документов за вызов. См. Вебхуки.
Предпросмотр фида перед импортом
POST /indexes/{indexSlug}/feed-preview (scope documents:write) читает XML, YAML, CSV или JSON по публичному URL и возвращает найденные поля, предложенный маппинг, образец результата и предложение схемы. В индекс ничего не записывается. Чтобы проверить свои правила, передайте mapping ({ "rules": [{ "source", "target", "transforms" }] }). Преобразования: strip_html, to_number, to_bool, split, join, default, lowercase, date_to_unix, first_non_empty. Нечитаемый фид даёт 422 FEED_UNREADABLE.
Подсказки
GET /api/v1/indexes/{slug}/suggest и POST /api/v1/indexes/suggest/multi — автодополнение по префиксу с учётом популярных запросов, с ETag и без списания квоты.
Аналитика
Обзор, экспорт в CSV, медленные запросы, тренды, алерты по пустой выдаче, связанные запросы, эксперименты и недавние поиски — всё из журнала запросов.