AACSearch
Справочник API

Документы

Загрузка, обновление, удаление и экспорт документов — 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.

On this page