Docs / API Reference

API Reference

All API endpoints use JSON format. Authentication uses Bearer aqn_ API key authentication. See Authentication for details.

Base URL: https://your-instance/api/


Authentication

Include your API key in all requests:

Authorization: Bearer aqn_your_api_key

API keys are created and managed from the API Keys page (/keys/). Full-access keys see all collections; scoped keys see only granted collections.


OpenAPI Schema

The full OpenAPI schema is available at /api/v1/schema/ in all environments (including production). Use it to integrate with any OpenAPI-compatible tool:

  • SDK generation (openapi-generator, kiota, etc.)
  • ChatGPT Custom GPT Actions
  • Postman / Insomnia import
  • Any OpenAPI-compatible tool or workflow
GET /api/v1/schema/

Returns a valid OpenAPI 3.0 document as JSON. No authentication required. Swagger UI (/api/v1/docs/) and ReDoc (/api/v1/redoc/) are available in debug/development environments only.


Collections

List Collections

GET /api/v1/collections/

Returns collections visible to the authenticated user (owned + subscribed public).

Response: 200 OK

[
  {
    "uuid": "a1b2c3...",
    "name": "Project Docs",
    "slug": "project-docs",
    "description": "Technical documentation",
    "document_count": 15,
    "total_chunks": 342,
    "is_public": false,
    "is_subscribed": false,
    "created_at": "2026-03-01T10:00:00Z"
  }
]

Create Collection

POST /api/v1/collections/

Create a new collection owned by the authenticated user.

Request Body:

Field Type Required Description
name string Yes Collection name (must be non-empty)
description string No Optional description

Response: 201 Created — Full collection object

{
  "uuid": "a1b2c3...",
  "name": "Project Docs",
  "slug": "project-docs",
  "description": "Technical documentation",
  "document_count": 0,
  "total_chunks": 0,
  "is_public": false,
  "is_subscribed": false,
  "created_at": "2026-03-23T10:00:00Z"
}

Errors:

  • 400 — Name is missing or empty

Example:

curl -X POST https://your-instance/api/v1/collections/ \
  -H "Authorization: Bearer aqn_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "Project Docs", "description": "Technical documentation"}'

Get Collection

GET /api/v1/collections/{uuid}/

Response: 200 OK — Collection object

Update Collection

PATCH /api/v1/collections/{uuid}/

Update a collection's name and/or description. Owner only.

Request Body: Any subset of the following fields:

Field Type Description
name string New collection name
description string New description

Response: 200 OK — Updated collection object

Errors:

  • 400 — Validation error (e.g. empty name)
  • 403 — Not the collection owner
  • 404 — Collection not found

Example:

curl -X PATCH https://your-instance/api/v1/collections/{uuid}/ \
  -H "Authorization: Bearer aqn_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "Renamed Docs"}'

Delete Collection

DELETE /api/v1/collections/{uuid}/

Soft-delete a collection and all its documents. Owner only. Search index entries are removed immediately; blobs are retained for a 7-day grace period before permanent deletion.

Response: 204 No Content

Errors:

  • 403 — Not the collection owner
  • 404 — Collection not found

Example:

curl -X DELETE https://your-instance/api/v1/collections/{uuid}/ \
  -H "Authorization: Bearer aqn_your_api_key" \
  -w "\nHTTP %{http_code}\n"

Documents

List Documents

GET /api/v1/documents/

Returns documents across all visible collections.

Query Parameters:

Param Type Description
tag string Filter by tag (case-insensitive contains)
collection string Filter by collection slug

Response: 200 OK

[
  {
    "uuid": "d4e5f6...",
    "title": "Annual Report 2025",
    "source_type": "pdf",
    "status": "indexed",
    "tags": ["finance", "annual"],
    "chunks_count": 24,
    "original_filename": "report-2025.pdf",
    "file_size": 102400,
    "collection_name": "Project Docs",
    "collection_slug": "project-docs",
    "created_at": "2026-03-01T10:00:00Z"
  }
]

Upload Documents

POST /api/v1/collections/{uuid}/documents/

Upload one or more documents to a collection.

Auth: Requires write permission — full-access keys or scoped keys with a write grant on the collection.

Request: multipart/form-data

Field Type Required Description
file file One of file/files Single file upload
files file[] One of file/files Batch upload (multiple files)
title string No Document title (defaults to filename)
tags string No Comma-separated list of tags
source_type string No File type override (auto-detected from extension if omitted)

Single file response: 201 Created

{
  "uuid": "d4e5f6...",
  "title": "Annual Report 2025",
  "source_type": "pdf",
  "status": "pending",
  "original_filename": "report-2025.pdf",
  "file_size": 102400,
  "tags": ["finance", "annual"],
  "collection_name": "Project Docs",
  "created_at": "2026-03-22T10:00:00Z"
}

Batch upload response: 201 Created

{
  "uploaded": [
    {
      "uuid": "d4e5f6...",
      "title": "Annual Report 2025",
      "source_type": "pdf",
      "status": "pending",
      "original_filename": "report-2025.pdf",
      "file_size": 102400,
      "tags": [],
      "collection_name": "Project Docs",
      "created_at": "2026-03-22T10:00:00Z"
    }
  ],
  "skipped": [
    {
      "filename": "image.png",
      "reason": "Unsupported file type."
    }
  ],
  "total_uploaded": 1,
  "total_skipped": 1
}

Errors:

  • 400 — No files provided, unsupported file type, or file too large
  • 403 — Write access required
  • 404 — Collection not found

Example — single file:

curl -X POST https://your-instance/api/v1/collections/{uuid}/documents/ \
  -H "Authorization: Bearer aqn_your_api_key" \
  -F "file=@report-2025.pdf" \
  -F "title=Annual Report 2025" \
  -F "tags=finance,annual"

Example — batch upload:

curl -X POST https://your-instance/api/v1/collections/{uuid}/documents/ \
  -H "Authorization: Bearer aqn_your_api_key" \
  -F "files=@report-2025.pdf" \
  -F "files=@summary.docx" \
  -F "tags=finance"

Replace Document

POST /api/v1/documents/{uuid}/replace/

Replace a document's file and re-process it. The document UUID is preserved; old search entries are removed and the new file is processed from scratch.

Auth: Requires write permission — full-access keys or scoped keys with a write grant on the collection.

Request: multipart/form-data

Field Type Required Description
file file Yes Replacement file
title string No Override document title (keeps existing title if omitted)
tags string No Comma-separated list of tags (replaces existing tags if provided)
source_type string No File type override (auto-detected from extension if omitted)

Response: 200 OK — Document object with status: "pending"

{
  "uuid": "d4e5f6...",
  "title": "Annual Report 2026",
  "source_type": "pdf",
  "status": "pending",
  "original_filename": "report-2026.pdf",
  "file_size": 110592,
  "tags": ["finance", "annual"],
  "collection_name": "Project Docs",
  "created_at": "2026-03-01T10:00:00Z"
}

Errors:

  • 400 — No file provided or unsupported file type
  • 403 — Write access required
  • 404 — Document not found

Example:

curl -X POST https://your-instance/api/v1/documents/{uuid}/replace/ \
  -H "Authorization: Bearer aqn_your_api_key" \
  -F "file=@report-2026.pdf" \
  -F "title=Annual Report 2026"

Download Document

GET /api/v1/documents/{uuid}/download/

Download the original uploaded file. Returns a 302 redirect to a time-limited signed URL (valid for 1 hour).

Auth: Any valid API key or session auth that can see the document — owned collections or subscribed public collections.

Response: 302 Found — Redirect to Azure Blob Storage SAS URL

Errors:

  • 404 — Document not found or no file available (text-only or URL documents have no stored file)

Example:

curl -L -H "Authorization: Bearer aqn_..." \
  https://aqoon.ai/api/v1/documents/{uuid}/download/ \
  -o file.pdf

Use the -L flag to follow the redirect automatically. The signed URL expires after 1 hour.

Get Document Detail

GET /api/v1/documents/{uuid}/

Response: 200 OK — Includes content_text, error_message, and chunks array.

{
  "uuid": "d4e5f6...",
  "title": "Annual Report 2025",
  "source_type": "pdf",
  "status": "indexed",
  "tags": ["finance", "annual"],
  "chunks_count": 24,
  "original_filename": "report-2025.pdf",
  "file_size": 102400,
  "content_text": "Full extracted text...",
  "error_message": "",
  "collection_name": "Project Docs",
  "collection_slug": "project-docs",
  "created_at": "2026-03-01T10:00:00Z",
  "chunks": [
    {
      "id": "uuid",
      "content": "Chunk text content...",
      "chunk_index": 0,
      "embedding_model": "text-embedding-3-small"
    }
  ]
}

POST /api/v1/search/

Request Body:

{
  "query": "payment terms",
  "collection": "project-docs",
  "limit": 5
}
Field Type Required Default
query string Yes
collection slug No All visible collections
limit int No 5 (max: 50)

Response: 200 OK

{
  "query": "payment terms",
  "count": 3,
  "results": [
    {
      "content": "Matching chunk text...",
      "title": "Contract Document",
      "document_id": "uuid",
      "collection_name": "Legal",
      "tags": ["contract"],
      "score": 0.89
    }
  ]
}

Collection Subscriptions

Subscribe to a Public Collection

POST /api/v1/collections/{slug}/subscribe/

Response: 200 OK

{
  "status": "subscribed",
  "collection": "project-docs"
}

Errors:

  • 404 — Collection not found or not public

Unsubscribe from a Collection

DELETE /api/v1/collections/{slug}/subscribe/

Response: 200 OK

{
  "status": "unsubscribed",
  "collection": "project-docs"
}

Errors:

  • 404 — Subscription not found

API Key Management (KaaS)

KaaS API keys allow external developers to search your collections. Managed via the API Keys page in the web UI or via the REST API.

List / Create API Keys

GET /api/v1/keys/
POST /api/v1/keys/

Create Request Body:

{
  "name": "Acme Corp Key"
}

Create Response: 201 Created

{
  "uuid": "key-uuid",
  "name": "Acme Corp Key",
  "prefix": "aqn_abc123...",
  "key": "aqn_full_key_here",
  "is_active": true,
  "created_at": "2026-03-01T10:00:00Z"
}

The key field is only returned at creation time.

Rotate API Key

POST /api/v1/keys/{uuid}/rotate/

Revokes the existing API key and creates a new one with the same name and collection access grants. The old key is immediately deactivated.

Response: 201 Created

{
  "uuid": "new-key-uuid",
  "name": "Acme Corp Key",
  "prefix": "aqn_abc123...",
  "key": "aqn_new_full_key_here",
  "is_active": true,
  "is_full_access": false,
  "created_at": "2026-03-13T10:00:00Z"
}

The key field is only returned at rotation time. Store it securely — it cannot be retrieved again.

Errors:

  • 404 — API key not found or not owned by the authenticated user

Grant / Revoke Collection Access

POST /api/v1/keys/{uuid}/collections/
DELETE /api/v1/keys/{uuid}/collections/{collection-slug}/

Grant Request Body:

Field Type Required Description
collection slug Yes Collection slug to grant access to
permission string No "read" (default) or "write"
{
  "collection": "my-collection",
  "permission": "write"
}

If a grant already exists for this key and collection, the permission level is updated.

List Grants Response: GET /api/v1/keys/{uuid}/collections/200 OK

[
  {
    "collection": "my-collection",
    "permission": "write"
  },
  {
    "collection": "public-docs",
    "permission": "read"
  }
]

Usage Stats

GET /api/v1/keys/usage/?days=30
GET /api/v1/keys/{uuid}/usage/

Webhooks

Webhooks let you receive real-time notifications when documents finish processing. Configure one webhook URL per organization to receive document.indexed and document.failed events. Payloads are signed with HMAC-SHA256 and delivered with exponential backoff retries on failure.

Get Webhook Config

GET /api/v1/webhooks/

Returns the current webhook configuration for the organization.

Response: 200 OK

{
  "configured": true,
  "url": "https://your-app.example.com/hooks/aqoon",
  "is_active": true
}

The secret is never returned after initial creation.

curl https://your-instance/api/v1/webhooks/ \
  -H "Authorization: Bearer aqn_your_api_key"

Create or Update Webhook

POST /api/v1/webhooks/

Creates a new webhook or updates the URL on an existing one.

Request Body:

{
  "url": "https://your-app.example.com/hooks/aqoon"
}

On create: 201 Created — secret returned once, store it securely.

{
  "configured": true,
  "url": "https://your-app.example.com/hooks/aqoon",
  "is_active": true,
  "secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

On update (webhook already exists): 200 OK — secret is not returned.

{
  "configured": true,
  "url": "https://your-app.example.com/hooks/aqoon-updated",
  "is_active": true
}
# Create
curl -X POST https://your-instance/api/v1/webhooks/ \
  -H "Authorization: Bearer aqn_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://your-app.example.com/hooks/aqoon"}'

# Update URL
curl -X POST https://your-instance/api/v1/webhooks/ \
  -H "Authorization: Bearer aqn_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://your-app.example.com/hooks/aqoon-v2"}'

Delete Webhook

DELETE /api/v1/webhooks/

Removes the webhook configuration. No more events will be delivered.

Response: 200 OK

curl -X DELETE https://your-instance/api/v1/webhooks/ \
  -H "Authorization: Bearer aqn_your_api_key"

Send Test Event

POST /api/v1/webhooks/test/

Queues a test event to be delivered to the configured webhook URL immediately. Returns 404 if no webhook is configured.

Response: 200 OK

curl -X POST https://your-instance/api/v1/webhooks/test/ \
  -H "Authorization: Bearer aqn_your_api_key"

Payload Format

Each event is delivered as a JSON POST to your webhook URL:

{
  "event": "document.indexed",
  "timestamp": "2026-03-23T10:00:00Z",
  "data": {
    "uuid": "a1b2c3...",
    "status": "indexed",
    "title": "Q1 Report",
    "source_type": "pdf",
    "collection_uuid": "d4e5f6...",
    "collection_name": "Finance Docs",
    "chunks_count": 42,
    "tags": "finance,quarterly",
    "error_message": null,
    "original_filename": "q1-report.pdf",
    "file_size": 204800
  }
}

The event field is either document.indexed (processing succeeded) or document.failed (processing failed). On failure, error_message contains a description.

Signature Verification

Every webhook request includes two headers for security:

  • X-Aqoon-Signature: sha256=<hex-digest>
  • X-Aqoon-Timestamp: <unix-timestamp>

The signature is an HMAC-SHA256 of {timestamp}.{raw-body} using your webhook secret. Always verify the signature before processing the payload.

import hashlib
import hmac

def verify_signature(secret: str, timestamp: str, body: bytes, signature: str) -> bool:
    message = f"{timestamp}.".encode() + body
    expected = hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)
import { createHmac, timingSafeEqual } from 'crypto';

function verifySignature(secret: string, timestamp: string, body: Buffer, signature: string): boolean {
  const message = Buffer.concat([Buffer.from(`${timestamp}.`), body]);
  const expected = 'sha256=' + createHmac('sha256', secret).update(message).digest('hex');
  return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

To guard against replay attacks, reject events where X-Aqoon-Timestamp is more than 5 minutes in the past.


MCP Endpoint

The MCP server is accessible at /mcp using Streamable HTTP transport.

Auth: Authorization: Bearer aqn_<key>

Tools Available:

Tool Description
search_documents Hybrid text + vector search with optional collection filter and result limit
list_collections List all collections with document counts and descriptions
get_document_info Get full document details and content chunks by UUID
search_by_tag Find all documents with a specific tag

Error Handling

HTTP Status Codes

Code Meaning
200 Success
201 Created
400 Bad request (validation error)
401 Unauthorized (missing or invalid token/API key)
404 Resource not found
429 Rate limited
500 Server error

Error Response Format

All errors use a consistent envelope with an error field and a machine-readable code field:

{
  "error": "Collection not found.",
  "code": "COLLECTION_NOT_FOUND"
}

Validation errors include a fields object with per-field details:

{
  "error": "Validation failed.",
  "code": "VALIDATION_FAILED",
  "fields": {
    "name": ["This field is required."],
    "collection": ["Collection not found."]
  }
}

Use code for programmatic error handling (e.g. switch / if logic in your client). Use error for display to end users.

Error Codes

401 — Authentication

Code Description
INVALID_API_KEY API key not found or hash mismatch
API_KEY_EXPIRED API key has passed its expiration date
USER_ACCOUNT_DISABLED The key's user account is inactive
AUTHENTICATION_REQUIRED No valid credentials provided

403 — Authorization

Code Description
WRITE_ACCESS_REQUIRED Scoped key needs write permission for this collection
NOT_COLLECTION_OWNER Only the collection owner can update or delete
SCOPED_KEY_FORBIDDEN Scoped keys cannot perform this action

404 — Not Found

Code Description
COLLECTION_NOT_FOUND Collection does not exist or is not visible to this key
DOCUMENT_NOT_FOUND Document does not exist or is not accessible
DOCUMENT_FILE_NOT_FOUND Document exists but the original file is missing from storage
API_KEY_NOT_FOUND API key record not found
GRANT_NOT_FOUND Collection access grant not found
SUBSCRIPTION_NOT_FOUND Collection subscription not found
WEBHOOK_NOT_CONFIGURED No webhook has been configured for this account

400 — Validation

Code Description
VALIDATION_FAILED One or more fields failed validation (see fields object)
NO_FILES_PROVIDED Batch upload request included no files
NO_FILE_PROVIDED Single upload request included no file
UNSUPPORTED_FILE_TYPE File extension or MIME type is not supported
FILE_TOO_LARGE File exceeds the 50 MB size limit
INVALID_FILE_CONTENT File content does not match the declared type (magic byte check)
URL_REQUIRED A URL source type was specified but no URL was provided
NAME_REQUIRED A required name field was empty or missing

429 — Rate Limiting

Code Description
RATE_LIMIT_EXCEEDED Request quota exceeded; check X-RateLimit-* headers for reset times

500 — Server Errors

Code Description
SEARCH_UNAVAILABLE Azure AI Search is temporarily unavailable
UPLOAD_FAILED File could not be written to blob storage
WEBHOOK_TEST_FAILED Test event delivery to the configured webhook URL failed