# Authentication Source: https://developers.ambersearch.de/api-reference/authentication Learn how to authenticate with the amber API using OAuth 2.0 ## Overview The amber Public API uses **OAuth 2.0 Authorization Code Grant and a PKCE safety measure** to authenticate third-party applications. This flow allows users to authorize external applications to access amber on their behalf without sharing their credentials. The provider abides by the specifications defined in the RFC 6749 and RFC 7636 protocols, implementing a seamless and secure interface for authorizing users. This is a beta version of our OAuth 2.0 implementation. Features may evolve as we continue to improve the API. ## Base URL All OAuth endpoints are available under: ``` https://customerDomain.ambersearch.de/api/oauth ``` ## OAuth 2.0 Flow The OAuth 2.0 Authorization Code Grant flow consists of the following steps: Your application redirects the user to `/api/oauth/authorize` with your client credentials and desired scopes. The user authenticates with amber (if not already logged in) and authorizes your application. amber redirects back to your application's redirect URI with an authorization code. Your application exchanges the authorization code for an access token by calling `/api/oauth/token`. Use the access token to make authenticated requests to amber API endpoints. *** ## Getting Started To use OAuth 2.0 with the amber API, you need OAuth client credentials `client_id`, `client_secret` or simply `client_id` if PKCE is required. **OAuth client credentials can only be obtained from the amber team.** Contact your dedicated customer success manager to request your credentials. You cannot generate these credentials yourself. *** ## Discovery Endpoints To make integration easier, amber exposes OAuth discovery endpoints so clients can automatically learn: • the authorization endpoint (/authorize) • the token endpoint (/token) • supported PKCE methods • supported scopes ```http theme={null} GET /.well-known/oauth-authorization-server ``` ## Authorization Endpoint ### Step 1: Redirect User to Authorization Direct users to the authorization endpoint to begin the OAuth flow: ```http theme={null} GET /api/oauth/authorize ``` **Query Parameters:** | Parameter | Type | Required | Description | | ----------------------- | ------ | ----------- | ---------------------------------------------------------------------------------------------------------------------- | | `response_type` | string | Yes | Must be `code` | | `client_id` | string | Yes | Your OAuth client ID | | `redirect_uri` | string | Yes | Must match one of the registered redirect URIs | | `scope` | string | No | Space-separated list of requested scopes | | `state` | string | Recommended | Opaque value for CSRF protection | | `code_challenge` | string | Yes | Encoded version of the code verifier | | `code_challenge_method` | string | Yes | Encoding algorithm that transforms code\_verifier into a code\_challenge, only 2 methods are supported, S256 and plain | **Example Authorization URL:** ``` https://customerDomain.ambersearch.de/api/oauth/authorize? response_type=code& client_id=550e8400-e29b-41d4-a716-446655440000& redirect_uri=https://myapp.example.com/callback& scope=offline_access& state=xyz123 code_challenge=a-code-challenge code_challenge_method=S256 ``` ### Step 2: User Authentication If the user is not logged into amber, they will be redirected to the login page. After successful authentication, they are redirected back to continue the OAuth flow. ### Step 3: Authorization Response Upon successful authorization, the user is redirected to your `redirect_uri` with: ``` https://myapp.example.com/callback?code=BASE64_ENCODED_AUTH_CODE&state=xyz123 ``` | Parameter | Description | | --------- | ------------------------------------------------------- | | `code` | The authorization code (valid for 10 minutes) | | `state` | The state parameter you provided (verify this matches!) | **Error Response:** ``` https://myapp.example.com/callback?error=access_denied&error_description=User+denied+access ``` *** ## Token Endpoint ### Step 4: Exchange Authorization Code for Access Token ```http theme={null} POST /api/oauth/token Content-Type: application/x-www-form-urlencoded ``` **Request Body (form-encoded):** | Parameter | Type | Required | Description | | --------------- | ------ | -------- | --------------------------------------------------------------------------- | | `grant_type` | string | Yes | `authorization_code` | | `code` | string | Yes | The authorization code received | | `redirect_uri` | string | Yes | Must match the authorization request | | `client_id` | string | Yes | Your OAuth client ID | | `code_verifier` | string | Yes | A randomly generated string used to verify the client request on the server | **Example Request:** ```bash theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=BASE64_ENCODED_AUTH_CODE" \ -d "redirect_uri=https://myapp.example.com/callback" \ -d "client_id=550e8400-e29b-41d4-a716-446655440000" \ -d "code_verifier=your-code-verifier" ``` **Success Response:** ```json theme={null} { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600, "scope": "" } ``` **Success Response (with offline\_access scope):** ```json theme={null} { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": null, "scope": "offline_access" } ``` | Field | Description | | --------------- | ----------------------------------------------------------- | | `access_token` | JWT access token for API requests | | `refresh_token` | JWT access token for generating access tokens | | `token_type` | Always `Bearer` | | `expires_in` | Token lifetime in seconds (3600 = 1 hour, null = permanent) | | `scope` | Granted scopes | *** ## Using the Access Token Once you have an access token, include it in the `Authorization` header for all API requests: ```bash theme={null} curl -X GET "https://customerDomain.ambersearch.de/api/beta/search?query=example" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` *** ## Step 5: Exchanging the refresh token for an access token In case of the access token expiring or for rotating the tokens you can pass the refresh token in order to retrieve a new access and refresh token | Parameter | Type | Required | Description | | --------------- | ------ | -------- | --------------------------------------------------------- | | `client_id` | string | Yes | Your OAuth client ID | | `client_secret` | string | Yes | Your OAuth client secret | | `grant_type` | string | Yes | `refresh_token` | | `refresh_token` | string | Yes | A jwt used for authentication and access token generation | curl -X POST "[https://customerDomain.ambersearch.de/api/oauth/token](https://customerDomain.ambersearch.de/api/oauth/token)" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client\_id=client\_id" \ -d "client\_secret"="client\_secret" \ -d "grant\_type=refresh\_token" \ -d "refresh\_token=your-refresh-token" **Success Response:** ```json theme={null} { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600, } ``` **Success Response (with offline\_access scope):** ```json theme={null} { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": null, } ``` *** ## Scopes | Scope | Description | | ---------------- | ------------------------------------------------ | | `offline_access` | Request a permanent access token (never expires) | **Token Expiration:** * **Standard tokens:** Expire after 1 hour (`expires_in: 3600`) * **Offline access tokens:** Never expire (`expires_in: null`) For long-running applications or background processes, request the `offline_access` scope to avoid token expiration issues. *** ## Error Handling Common OAuth errors: | Error | Description | | --------------------- | ----------------------------------------------------------- | | `invalid_request` | The request is missing a required parameter or is malformed | | `unauthorized_client` | The client is not authorized to use this flow | | `access_denied` | The user denied the authorization request | | `invalid_grant` | The authorization code is invalid or expired | | `invalid_client` | Client authentication failed | **Example Error Response:** ```json theme={null} { "error": "invalid_grant", "error_description": "The authorization code has expired" } ``` *** ## Security Best Practices * Always use HTTPS for redirect URIs * Validate the `state` parameter to prevent CSRF attacks * Rotate access tokens regularly if not using offline\_access * Switch between the `code_verifier` strings constantly * Never expose tokens in URLs or logs # Download document Source: https://developers.ambersearch.de/api-reference/endpoint/download-document get /api/beta/document/download Download a document by its ID. This endpoint allows downloading a document’s content using its unique identifier. The response will be the actual file content with appropriate content type headers. The document ID is passed as the `document_id` query parameter and must be URL-encoded. Use your HTTP client’s parameter support rather than building the query string by hand — IDs of documents from SMB / Windows file shares are file paths and may contain `/`, spaces and `&`. **Breaking change:** the ID was previously part of the URL path; the old `/document/{document_id}/download` form is no longer supported. ```bash cURL theme={null} curl -G "https://customerDomain.ambersearch.de/api/beta/document/download" \ --data-urlencode "document_id={document_id}" \ -H "Authorization: Bearer ambrs-exampletoken" ``` ```python Python theme={null} import requests response = requests.get( "https://customerDomain.ambersearch.de/api/beta/document/download", params = { "document_id": "{document_id}" }, headers = { "Authorization": "Bearer ambrs-exampletoken" } ) print(response.content) ``` ```javascript Javascript theme={null} const axios = require("axios"); axios .get("https://customerDomain.ambersearch.de/api/beta/document/download", { params: { document_id: "{document_id}", }, headers: { Authorization: "Bearer ambrs-exampletoken", } }) .then((res) => console.log(res.data)); ``` # Get document metadata Source: https://developers.ambersearch.de/api-reference/endpoint/get-document-metadata get /api/beta/document/metadata Retrieve metadata for a specific document. This endpoint returns the metadata for a document identified by its ID. Optionally, it can include the document’s text content. The document ID is passed as the `document_id` query parameter and must be URL-encoded. Use your HTTP client’s parameter support rather than building the query string by hand — IDs of documents from SMB / Windows file shares are file paths and may contain `/`, spaces and `&`. **Breaking change:** the ID was previously part of the URL path; the old `/document/{document_id}/metadata` form is no longer supported. ```bash cURL theme={null} curl -G "https://customerDomain.ambersearch.de/api/beta/document/metadata" \ --data-urlencode "document_id={document_id}" \ -H "Authorization: Bearer ambrs-exampletoken" ``` ```python Python theme={null} import requests response = requests.get( "https://customerDomain.ambersearch.de/api/beta/document/metadata", params = { "document_id": "{document_id}" }, headers = { "Authorization": "Bearer ambrs-exampletoken" } ) print(response.json()) ``` ```javascript Javascript theme={null} const axios = require("axios"); axios .get("https://customerDomain.ambersearch.de/api/beta/document/metadata", { params: { document_id: "{document_id}", }, headers: { Authorization: "Bearer ambrs-exampletoken", } }) .then((res) => console.log(res.data)); ``` # Search full text Source: https://developers.ambersearch.de/api-reference/endpoint/search get /api/beta/search Perform a full text search across documents. This endpoint allows searching through documents with various filtering options. The search results are cleaned to include only whitelisted attributes. ```bash cURL theme={null} curl "https://customerDomain.ambersearch.de/api/beta/search?query=example&count=50" \ -H "Authorization: Bearer ambrs-exampletoken" ``` ```python Python theme={null} import requests response = requests.get( "https://customerDomain.ambersearch.de/api/beta/search", headers = { "Authorization": "Bearer ambrs-exampletoken" }, params = { "query": "example", "count": 50 } ) print(response.json()) ``` ```javascript Javascript theme={null} const axios = require("axios"); axios .get("https://customerDomain.ambersearch.de/api/beta/search", { headers: { Authorization: "Bearer ambrs-exampletoken", }, params: { query: "example", count: 50, }, }) .then((res) => console.log(res.data)); ``` # Upload File Source: https://developers.ambersearch.de/api-reference/endpoint/upload-file post /api/beta/files/upload Upload a file for use with AI chat completions. This endpoint allows uploading documents or images that can later be referenced in chat completion requests via the metadata.files parameter. ```bash cURL theme={null} curl "https://customerDomain.ambersearch.de/api/beta/files/upload?type=document" \ -H "Authorization: Bearer ambrs-exampletoken" \ -F file="@myfile.pdf" ``` ```python Python theme={null} import requests response = requests.posts( "https://customerDomain.ambersearch.de/api/beta/files/upload?type=document", headers = { "Authorization": "Bearer ambrs-exampletoken" }, files = { "file": open("myfile.pdf", "rb") } ) print(response.json()) ``` ```javascript Javascript theme={null} const axios = require("axios"); axios .post("https://customerDomain.ambersearch.de/api/beta/files/upload?type=document", { headers: { Authorization: "Bearer ambrs-exampletoken", }, files: { file: fs.createReadStream("myfile.pdf"), }, }) .then((res) => console.log(res.data)); ``` # Web search Source: https://developers.ambersearch.de/api-reference/endpoint/web-search post /api/beta/web_search Perform a web search using the provided queries and parameters. This endpoint allows searching the web with specified queries, sites, and other options. ```bash cURL theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/beta/web_search" \ -H "Authorization: Bearer ambrs-exampletoken" \ -H "Content-Type: application/json" \ -d '{ "queries": ["latest developments in AI"], "sites": ["techcrunch.com", "wired.com"], "count": 5, "mode": "search" }' ``` ```python Python theme={null} import requests response = requests.post( "https://customerDomain.ambersearch.de/api/beta/web_search", headers = { "Authorization": "Bearer ambrs-exampletoken" }, json = { "queries": ["latest developments in AI"], "sites": ["techcrunch.com", "wired.com"], "count": 5, "mode": "context" } ) print(response.json()) ``` ```javascript Javascript theme={null} const axios = require("axios"); axios .post( "https://customerDomain.ambersearch.de/api/beta/web_search", { queries: ["latest developments in AI"], sites: ["techcrunch.com", "wired.com"], count: 5, mode: "perplexity", }, { headers: { Authorization: "Bearer ambrs-exampletoken", }, }, ) .then((res) => console.log(res.data)); ``` ```json Response theme={null} [ { "url": "https://techcrunch.com/category/artificial-intelligence", "metadata": { "title": "AI News & Artificial Intelligence | TechCrunch", "description": "Read the latest on artificial intelligence and machine learning tech, the companies that are building them, and the ethical issues AI raises today." }, "content": "News coverage on artificial intelligence and machine learning tech, the companies building them, and the ethical issues AI raises today. This encompasses generative AI, including large language models, text-to-image and text-to-video models; speech recognition and generation; and predictive analytics." }, { "url": "https://techcrunch.com/2026/01/02/in-2026-ai-will-move-from-hype-to-pragmatism", "metadata": { "title": "In 2026, AI will move from hype to pragmatism | TechCrunch", "description": "That’s why many researchers believe the next big leap will come from world models: AI systems that learn how things move and interact in 3D spaces so they can make predictions and take actions." }, "content": "Today, many researchers think the AI industry is beginning to exhaust the limits of scaling laws and will once again transition into an age of research. Yann LeCun, Meta’s former chief AI scientist, has long argued against the overreliance on scaling, and stressed the need to develop better architectures. In 2026, here's what you can expect from the AI industry: new architectures, smaller models, world models, reliable agents, physical AI, and products designed for real-world use. That’s why many researchers believe the next big leap will come from world models: AI systems that learn how things move and interact in 3D spaces so they can make predictions and take actions. Signs that 2026 will be a big year for world models are multiplying. LeCun left Meta to start his own world model lab and is reportedly seeking a $5 billion valuation. Google’s DeepMind has been plugging away at Genie and in August launched its latest model that builds real-time interactive general-purpose world models. The focus is already shifting away from building ever-larger language models and toward the harder work of making AI usable. In practice, that involves deploying smaller models where they fit, embedding intelligence into physical devices, and designing systems that integrate cleanly into human workflows." }, { "url": "https://techcrunch.com/tag/generative-ai", "metadata": { "title": "Generative AI news and analysis | TechCrunch", "description": "TechCrunch covers the latest news and analysis in Generative AI (GenAI). Read the latest on startups and founders building artificial intelligence tools." }, "content": "TechCrunch covers the latest news and analysis in Generative AI (GenAI). Read the latest on startups and founders building artificial intelligence tools." }, { "url": "https://techcrunch.com/2025/05/29/inside-the-ai-revolution-top-insights-and-breakthroughs-from-our-partners-at-techcrunch-sessions-ai", "metadata": { "title": "Inside the AI revolution: Top insights and breakthroughs from our partners at TechCrunch Sessions: AI | TechCrunch", "description": "Discover how AI-powered conversational tools now help Toyota technicians instantly access complex repair data — boosting productivity and dealership efficiency through smarter, faster workflows. Don’t miss “Building Richer and More Scalable GenAI Applications for Startups and Developers” from 2:25 p.m." }, "content": "This high-energy event brings together founders, investors, and technologists to explore how AI is transforming industries — from the pitch deck to full-scale deployment. We’re proud to spotlight our partners — not just for their support, but also for the expertise they bring to the stage. Their sessions offer a front-row view into the latest developments in AI and are a must-attend for anyone looking to stay ahead in this fast-evolving space. Discover how AI-powered conversational tools now help Toyota technicians instantly access complex repair data — boosting productivity and dealership efficiency through smarter, faster workflows." } ] ``` You can use the `deep_search` parameter to enable a more comprehensive search that includes the full content of web pages, rather than just snippets. This can provide more detailed results but may take longer to process. ## Web search modes The optional `mode` parameter selects which underlying search engine handles the request: | Mode | Description | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `search` (default) | Runs a standard Brave web search and crawls the resulting pages for their full content. | | `context` | Uses Brave's LLM Context API to return content optimized for grounding an LLM response, without crawling individual pages. The first result contains a synthesized summary, followed by grounded source snippets. | | `perplexity` | Uses the Perplexity Search API to return results. | All modes return the same response shape (a list of results with `url`, `metadata`, and `content`). If `mode` is omitted, the tenant's configured default is used, falling back to `search` if no default is set. # Introduction Source: https://developers.ambersearch.de/api-reference/introduction Welcome to amber Beta Public REST API ## Welcome to amber API Welcome to the beta version of amber's public REST API. This API provides programmatic access to amber's core functionality, allowing you to integrate our search capabilities into your applications. This is a beta version of our API. We are actively developing and extending the available endpoints. Some features may change as we continue to improve the API. ## Authentication The amber API supports two authentication methods: ### 1. API Key Authentication (Recommended for Quick Start) The simplest way to authenticate is using an API key. You can generate your API key directly in the amber UI under your user profile settings. ```bash theme={null} curl -X GET "https://customerDomain.ambersearch.de/api/beta/search?query=example" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **Quick Start:** Create your API key directly in the amber UI: 1. Log in to amber 2. Navigate to your user profile 3. Go to API Keys section 4. Generate a new API key ### 2. OAuth 2.0 Authentication (For Third-Party Applications) For third-party applications that need to access amber on behalf of users, we support OAuth 2.0 Authorization Code Grant flow. OAuth 2.0 requires development effort on the customer side to implement the authorization flow. This method is recommended for: * Third-party integrations * Multi-user applications * Applications that need delegated access Learn how to implement OAuth 2.0 authentication ## Base URL All API endpoints are relative to the base URL: ``` https://customerDomain.ambersearch.de/api/beta ``` ## Rate Limits To ensure fair usage and optimal performance for all users, our API implements rate limiting. Specific rate limits will be provided with your API key. ## Support If you encounter any issues or have questions about using the API, please contact our support team. We're here to help you make the most of amber's capabilities. View the complete API reference # Create chat completion Source: https://developers.ambersearch.de/api-reference/openai/create-chat-completion post /api/beta/amberai/chat/completions Create a chat completion based on the provided messages and parameters. This endpoint generates responses using the specified model and conversation history. You can also utilize tools and agents to enhance the response generation. ```python Default theme={null} import requests response = requests.request( "POST", "https://customerDomain.ambersearch.de/api/beta/amberai/chat/completions", headers={ "Authorization": "Bearer ambrs-exampletoken" }, json={ "model": "azure/gpt-5.4", "temperature": 0.7, "stream": False, "tools": [], "messages": [ {"role": "system", "content": "You are a helpful assistant."}, { "role": "user", "content": "What would be the best activity to do in summer in Paris?", }, ], }, ) ``` ```python Streaming theme={null} import requests response = requests.request( "POST", "https://customerDomain.ambersearch.de/api/beta/amberai/chat/completions", headers={ "Authorization": "Bearer ambrs-exampletoken" }, json={ "model": "azure/gpt-5.4", "temperature": 0.7, "stream": True, "tools": [], "messages": [ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": "What is the capital of France?" } ] } ) ``` ```python Tools theme={null} import requests response = requests.request( "POST", "https://customerDomain.ambersearch.de/api/beta/amberai/chat/completions", headers={ "Authorization": "Bearer ambrs-exampletoken" }, json={ "model": "azure/gpt-5.4", "temperature": 0.7, "stream": False, "tools": [ { "function": { "name": "amberSearch" } } ], "messages": [ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": "What are the credentials for the postman development environment?" } ] } ) ``` ```python Web search mode theme={null} import requests response = requests.request( "POST", "https://customerDomain.ambersearch.de/api/beta/amberai/chat/completions", headers={ "Authorization": "Bearer ambrs-exampletoken" }, json={ "model": "azure/gpt-5.4", "temperature": 0.7, "stream": False, "tools": [ { "function": { "name": "webSearch" } } ], "metadata": { "web_search_mode": "perplexity" }, "messages": [ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": "What are the latest developments in AI?" } ] } ) ``` ```python Agents theme={null} import requests response = requests.request( "POST", "https://customerDomain.ambersearch.de/api/beta/amberai/chat/completions", headers={ "Authorization": "Bearer ambrs-exampletoken" }, json={ "model": "azure/gpt-5.4", "temperature": 0.7, "stream": False, "tools": [], "metadata": { "agent_id": "9676b2b0-9fdd-40b1-8a0a-0debe64b29ac" }, "messages": [ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": "How does web search in amberise work?" } ] } ) ``` ```python Files theme={null} import requests response = requests.request( "POST", "https://customerDomain.ambersearch.de/api/beta/amberai/chat/completions", headers={ "Authorization": "Bearer ambrs-exampletoken" }, json={ "model": "azure/gpt-5.4", "temperature": 0.7, "stream": False, "metadata": { "files": ["01234567-89ab-cdef-0123-456789abcdef"] }, "messages": [ { "role": "system", "content": "Use the uploaded files to answer user's questions." }, { "role": "user", "content": "How does web search in amberise work?" } ] } ) ``` # Create chat completion (OpenAI format) Source: https://developers.ambersearch.de/api-reference/openai/create-chat-completion-openai-format post /api/beta/openai/v1/chat/completions Creates a chat completion in OpenAI-compatible format. This endpoint is designed to accept messages and parameters in the OpenAI format, meaning that you can use it with existing OpenAI-compatible clients and libraries. In contrast to our standard chat completion endpoint, this one runs independently of amber functionalities, and is intended for users who want to use OpenAI-compatible tools and libraries without relying on amber-specific features. For reference, see the [OpenAI Create chat completion documentation](https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create). ```python Default theme={null} from openai import OpenAI client = OpenAI( base_url="https://customerDomain.ambersearch.de/api/beta/openai/v1", api_key="ambrs-exampletoken" ) completion = client.chat.completions.create( model="azure/gpt-5.4", messages=[ {"role": "developer", "content": "You are a helpful assistant."}, {"role": "user", "content": "Hello!"} ] ) print(completion.choices[0].message) ``` ```python Image input theme={null} from openai import OpenAI client = OpenAI( base_url="https://customerDomain.ambersearch.de/api/beta/openai/v1", api_key="ambrs-exampletoken" ) response = client.chat.completions.create( model="azure/gpt-5.4", messages=[ { "role": "user", "content": [ {"type": "text", "text": "What's in this image?"}, { "type": "image_url", "image_url": { "url": "https://amber.de/uploads/amber_Team_73637a5596.jpg", } }, ], } ], max_tokens=300, ) print(response.choices[0]) ``` ```python Streaming theme={null} from openai import OpenAI client = OpenAI( base_url="https://customerDomain.ambersearch.de/api/beta/openai/v1", api_key="ambrs-exampletoken" ) completion = client.chat.completions.create( model="azure/gpt-5.4", messages=[ {"role": "developer", "content": "You are a helpful assistant."}, {"role": "user", "content": "Hello!"} ], stream=True ) for chunk in completion: print(chunk.choices[0].delta) ``` ```python Functions theme={null} from openai import OpenAI client = OpenAI( base_url="https://customerDomain.ambersearch.de/api/beta/openai/v1", api_key="ambrs-exampletoken" ) tools = [ { "type": "function", "function": { "name": "get_current_weather", "description": "Get the current weather in a given location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state, e.g. Aachen, North Rhine-Westphalia", }, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, }, "required": ["location"], }, } } ] messages = [{"role": "user", "content": "What's the weather like in Aachen today?"}] completion = client.chat.completions.create( model="azure/gpt-5.4", messages=messages, tools=tools, tool_choice="auto" ) print(completion) ``` ```python Default theme={null} ChatCompletionMessage( content='Hello! How can I help you today?', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None ) ``` ```python Image input theme={null} Choice( finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage( content='This image shows a large group of people posing outdoors on a sloped stone walkway or steps, surrounded by trees. Most of them are wearing matching blue sweatshirts, suggesting they may be part of a team, company, or group event. Many are raising their hands or making cheerful poses, so it looks like a casual group photo.\n\nOn the left side, there’s part of a building and a sign that appears to say “SUMIANI HOTEL & BEACH.” The setting looks sunny and wooded, possibly at a hotel or retreat location.', refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=None ) ) ``` ```python Streaming theme={null} ChoiceDelta(content='Hello', function_call=None, refusal=None, role=None, tool_calls=None) ChoiceDelta(content='!', function_call=None, refusal=None, role=None, tool_calls=None) ChoiceDelta(content=' How', function_call=None, refusal=None, role=None, tool_calls=None) ChoiceDelta(content=' can', function_call=None, refusal=None, role=None, tool_calls=None) ChoiceDelta(content=' I', function_call=None, refusal=None, role=None, tool_calls=None) ChoiceDelta(content=' help', function_call=None, refusal=None, role=None, tool_calls=None) ChoiceDelta(content=' you', function_call=None, refusal=None, role=None, tool_calls=None) ChoiceDelta(content=' today', function_call=None, refusal=None, role=None, tool_calls=None) ChoiceDelta(content='?', function_call=None, refusal=None, role=None, tool_calls=None) ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=None) ``` ```python Functions theme={null} ChatCompletion( id='chatcmpl-DufEoQk1y55a5G8XzKBnAe63ceFdA', choices=[ Choice( finish_reason='tool_calls', index=0, logprobs=None, message=ChatCompletionMessage( content=None, refusal=None, role='assistant', annotations=None, audio=None, function_call=None, tool_calls=[ ChatCompletionMessageFunctionToolCall( id='call_Tug3jwD1JpmzTapCFFWmYw7i', function=Function( arguments='{"location":"Aachen, North Rhine-Westphalia","unit":"celsius"}', name='get_current_weather' ), type='function', index=0 ) ] ) ) ], created=1782397258, model='gpt-5.4-2026-03-05', object='chat.completion', service_tier='default', system_fingerprint='', usage=CompletionUsage( completion_tokens=31, prompt_tokens=164, total_tokens=195, completion_tokens_details=CompletionTokensDetails( accepted_prediction_tokens=None, audio_tokens=None, reasoning_tokens=None, rejected_prediction_tokens=None ), prompt_tokens_details=PromptTokensDetails( audio_tokens=None, cached_tokens=0 ) ) ) ``` # Introduction Source: https://developers.ambersearch.de/api-reference/openai/introduction This document provides an overview of the OpenAI API compatible endpoints provided by amber. We have implemented the following endpoints to be compatible with the OpenAI API, allowing you to use amber as a drop-in replacement for OpenAI's API. This means you can use amber in applications that are designed to work with OpenAI's API without any modifications. Upon successful integration, you will also be able to use our custom tools, such as `amberSearch` and `webSearch`, to enhance your application's capabilities. The base URL for the API is: ``` https://customerDomain.ambersearch.de/api/beta/amberai ``` Let's start with the endpoints that we support, the authentication method, and how to use the chat completion endpoint with our tools and agents. ## Supported Endpoints * GET /models * GET /models/ * POST /chat/completions Other endpoints that are not listed here are not supported at the moment. We've also added the rest of the endpoints regarding the models and chat completions, but they are not functional and will return an error if you try to use them. ## Authentication Authorization for the endpoints is the same as other Endpoints. You need to provide an API key in the `Authorization` header of your request. The API key should be prefixed with `Bearer `. ```bash theme={null} curl "https://customerDomain.ambersearch.de/api/beta/amberai/models" \ -H "Authorization: Bearer ambrs-exampletoken" ``` ## Models Model endpoints are for getting the model identifiers, which are needed to create chat completions. Each model has a unique identifier, and you can retrieve the list of available models or details about a specific model using the following endpoints: * `GET /models` to list all available models. * `GET /models/{model}` to get details about a specific model. ## Chat Completions The chat completions endpoint is used to generate responses based on a given set of messages. For getting chat completions, you can specify the model you want to use, the messages that form the conversation, and various parameters to control the generation process. It mirrors the same flow that would happen if similar inputs (messages, tools, model) were provided via the amber frontend. Let's take a look at the endpoints: ### Create Chat Completion The endpoint for creating chat completions is: ``` POST /chat/completions ``` We only support the following parameters in the request body: * messages: `list` * model: `str` * stream: `bool` * temperature: `float` * metadata: `dict` * tools: `list` ### Messages We follow the same structure as OpenAI for the messages parameter, which is a list of message objects. Each message object should have a `role` and `content`. The roles can be one of the following: * `system` * `user` * `assistant` ## Tools We have two tools supported at the moment: `amberSearch` and `webSearch`. If no tool is passed, the model cannot perform any search in external or internal data sources. It can still e.g. give an answer based on its own knowledge only, or process an uploaded file (if it is passed as described below). To specify which tool should be used, simply pass it in the request body as follows: ```json theme={null} { "tools": [ { "function": { "name": "webSearch" } } ] } ``` The model can still decide to not use the tool if not needed for the given request. ## Agents To create chat completions with an agent, you need to specify the agent ID in the metadata of the request body as follows: ```json theme={null} { "metadata": { "agent_id": "9676b2b0-9fdd-40b1-8a0a-0debe64b29ac" } } ``` ## Files To attach files to the chat completion request, you can use the `files` parameter in the metadata of the request body as follows: ```json theme={null} { "metadata": { "files": ["01234567-89ab-cdef-0123-456789abcdef"] } } ``` The value of the `files` parameter should be a list of file IDs that you have previously received when uploading files to amber via the `/api/beta/files/upload` endpoint. ## Web search mode When the `webSearch` tool is enabled, you can control which search engine it uses for this request via the `web_search_mode` parameter in the metadata of the request body as follows: ```json theme={null} { "metadata": { "web_search_mode": "perplexity" } } ``` The `web_search_mode` parameter accepts one of the following values: * `search` - a standard Brave web search that also crawls the resulting pages for their full content. * `context` - Brave's LLM Context API, which returns content optimized for grounding an LLM response without crawling individual pages. * `perplexity` - the Perplexity Search API. If `web_search_mode` is omitted (or set to an empty string), the tenant's configured default is used, falling back to `search` if no default is set. This parameter has no effect if the `webSearch` tool is not included in `tools`. ## Language To specify which language your messages are in, you can use the `language` parameter in the metadata of the request body as follows: ```json theme={null} { "metadata": { "language": "en" } } ``` The default value for the `language` parameter is `de` (German). If you don't specify it, the system will assume that the messages are in German. If you want to use English, you need to explicitly set the `language` parameter to `en`. This is mostly relevant for very short user inputs, where the model has to 'guess' the language it should respond in. # List models Source: https://developers.ambersearch.de/api-reference/openai/list-models get /api/beta/amberai/models Retrieve a list of available AI models. This endpoint is for getting the model identifiers, which are needed to create chat completions. ```bash cURL theme={null} curl "https://customerDomain.ambersearch.de/api/beta/amberai/models" \ -H "Authorization: Bearer ambrs-exampletoken" ``` ```python Python theme={null} import requests response = requests.get( "https://customerDomain.ambersearch.de/api/beta/amberai/models", headers = { "Authorization": "Bearer ambrs-exampletoken" } ) print(response.json()) ``` ```javascript Javascript theme={null} const axios = require("axios"); axios .get("https://customerDomain.ambersearch.de/api/beta/amberai/models", { headers: { Authorization: "Bearer ambrs-exampletoken", }, }) .then((res) => console.log(res.data)); ``` # List models (OpenAI format) Source: https://developers.ambersearch.de/api-reference/openai/list-models-openai-format get /api/beta/openai/v1/models Retrieves a list of available models in OpenAI format. ```python List models theme={null} from openai import OpenAI client = OpenAI( base_url="https://customerDomain.ambersearch.de/api/beta/openai/v1", api_key="ambrs-exampletoken" ) for model in client.models.list(): print(model.id) ``` ```plain theme={null} azure/gpt-5.2 azure/gpt-5.4 azure/gpt-5.4-mini azure/gpt-5.5 azure/gpt-image-1 azure/gpt-image-1.5 azure/gpt-image-2 mistral/mistral-large-2512 telekom/Qwen3-Next-80B-A3B-Instruct-FP8 telekom/claude-opus-4.6 telekom/claude-sonnet-4.6 telekom/gemini-2.5-flash-image telekom/gpt-5-amber telekom/gpt-5-mini-amber vertex/claude-opus-4-8 vertex/gemini-3-flash-preview vertex/gemini-3.1-flash-lite vertex/gemini-3.1-pro-preview vertex/gemini-3.5-flash ``` # Retrieve model Source: https://developers.ambersearch.de/api-reference/openai/retrieve-model get /api/beta/amberai/models/{model} Retrieve details about a specific model by its identifier. This endpoint allows you to get information about a particular model available. ```bash cURL theme={null} curl "https://customerDomain.ambersearch.de/api/beta/amberai/models/{model}" \ -H "Authorization: Bearer ambrs-exampletoken" ``` ```python Python theme={null} import requests response = requests.get( "https://customerDomain.ambersearch.de/api/beta/amberai/models/{model}", headers = { "Authorization": "Bearer ambrs-exampletoken" } ) print(response.json()) ``` ```javascript Javascript theme={null} const axios = require("axios"); axios .get("https://customerDomain.ambersearch.de/api/beta/amberai/models/{model}", { headers: { Authorization: "Bearer ambrs-exampletoken", } }) .then((res) => console.log(res.data)); ``` # Custom Properties Source: https://developers.ambersearch.de/indexing-api/custom-properties Custom properties are documented together with datasource setup. ## Content moved Custom property **schemas** (declared on the datasource via `object_definitions` → `property_definitions`) and **values** (sent as `custom_properties` on each document) are covered in one place: Open the combined guide Quick links inside that page: * [Custom attributes (property definitions)](/indexing-api/setup-datasource#custom-attributes-property-definitions) * [Property definition fields](/indexing-api/setup-datasource#property-definition-fields) * [Property types](/indexing-api/setup-datasource#property-types) * [Sending values on documents](/indexing-api/index-documents#custom-properties-on-documents) * [Schema validation & errors](/indexing-api/index-documents#schema-validation--errors) # Index Documents Source: https://developers.ambersearch.de/indexing-api/index-documents Push documents into amber to make them searchable through the Indexing API. **Indexing API (BETA)** --- [Overview](/indexing-api/introduction) · [Datasource & properties](/indexing-api/setup-datasource) · [Index documents](/indexing-api/index-documents) · [Permissions](/indexing-api/permissions) ## Overview Once your datasource is configured, you can push documents to `POST /documents`. Each document is identified by a unique `id` within its datasource. Indexing a document with an existing `id` will update it. The endpoint accepts **either a single document object or an array** in the same request body — there is no separate bulk route. Wrap your payload in `documents`: ```json theme={null} { "documents": { ... } } // single { "documents": [ { ... }, { ... } ] } // bulk ``` For **searchable content**, you choose one of two approaches per document: 1. **Send file binaries** --- Post the file as Base64-encoded bytes (`binary_base64`) with the correct `mime_type`. amber handles content extraction server-side. 2. **Send text directly** --- Post `text_content` with the content you want indexed. Details: [Content extraction (two options)](#content-extraction-two-options). ## Index a single document Send a `POST` request to the `/documents` endpoint: ```bash cURL theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/indexing/documents" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "documents": { "datasource": "internal_wiki", "id": "wiki-getting-started", "title": "Getting Started with the Internal Wiki", "body": { "mime_type": "text/plain", "text_content": "This guide will help you navigate and contribute to our internal wiki..." }, "file_type": "wiki", "path_preview": "https://wiki.company.com/getting-started", "path": "Engineering / Runbooks / Getting Started", "author": "alice@company.com", "last_modified": "2026-04-09T12:00:00Z", "data_source_sub": "engineering", "data_source_sub_sub": "runbooks", "permissions": { "allow_anonymous_access": true }, "custom_properties": [ { "name": "department", "value": "Engineering" } ] } }' ``` ```python Python theme={null} import requests response = requests.post( "https://customerDomain.ambersearch.de/api/indexing/documents", headers={ "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, json={ "documents": { "datasource": "internal_wiki", "id": "wiki-getting-started", "title": "Getting Started with the Internal Wiki", "body": { "mime_type": "text/plain", "text_content": "This guide will help you navigate and contribute to our internal wiki...", }, "file_type": "wiki", "path_preview": "https://wiki.company.com/getting-started", "path": "Engineering / Runbooks / Getting Started", "author": "alice@company.com", "last_modified": "2026-04-09T12:00:00Z", "data_source_sub": "engineering", "data_source_sub_sub": "runbooks", "permissions": {"allow_anonymous_access": True}, "custom_properties": [ {"name": "department", "value": "Engineering"} ], } }, ) print(response.json()) ``` ```javascript Javascript theme={null} const axios = require("axios"); axios .post( "https://customerDomain.ambersearch.de/api/indexing/documents", { documents: { datasource: "internal_wiki", id: "wiki-getting-started", title: "Getting Started with the Internal Wiki", body: { mime_type: "text/plain", text_content: "This guide will help you navigate and contribute to our internal wiki...", }, file_type: "wiki", path_preview: "https://wiki.company.com/getting-started", path: "Engineering / Runbooks / Getting Started", author: "alice@company.com", last_modified: "2026-04-09T12:00:00Z", data_source_sub: "engineering", data_source_sub_sub: "runbooks", permissions: { allow_anonymous_access: true }, custom_properties: [ { name: "department", value: "Engineering" }, ], }, }, { headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, } ) .then((res) => console.log(res.data)); ``` ```json 200 theme={null} { "status": "ok", "datasource": "internal_wiki", "documents_received": null, "message": "document sent to the indexing pipeline.", "results": [ { "document_id": "wiki-getting-started", "outcome": "sent", "message": "document sent to the indexing pipeline." } ] } ``` ### Per-document outcomes The pipeline returns one of these outcomes for each document so you can act on duplicates and skipped items: | `outcome` | Meaning | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `sent` | New or changed --- queued into the indexing pipeline. | | `already_indexed` | The same `id` is already in Solr with the same `last_modified` **and** the same `allow_token_document` set; nothing was queued. | | `skipped` | Pipeline rejected the document (e.g. computed file suffix longer than 10 chars). The `message` explains why. | | `failed` | Validation or runtime error. Only appears in bulk responses; single-document errors come back as HTTP errors. | ### Response codes for `POST /documents` The HTTP response is **200** for any well-formed request, even if individual documents fail — inspect `results[*].outcome` for per-document state. | Code | When | | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **200 OK** | Request accepted. Per-doc state lives in `results[*].outcome`. | | **404 Not Found** | *(single-doc form only)* the referenced `datasource` does not exist. In the bulk form this becomes a per-doc `failed` entry. | | **422 Unprocessable Entity** *(service envelope)* | *(single-doc form only)* custom-property or sub-reference schema violation — see [Schema validation & errors](#schema-validation-errors). In the bulk form this becomes a per-doc `failed` entry. | ## Index multiple documents Send an array under `documents`. The server processes them sequentially and returns one result per document. **A failure on one document does not abort the rest** — each entry's outcome is reported individually. ```bash theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/indexing/documents" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "documents": [ { "datasource": "internal_wiki", "id": "doc-002", "title": "Second Document", "body": { "mime_type": "text/plain", "text_content": "Content of doc two." }, "file_type": "wiki", "path_preview": "https://wiki.company.com/doc-002", "last_modified": "2026-04-09T12:00:00Z" }, { "datasource": "internal_wiki", "id": "doc-003", "title": "Third Document", "body": { "mime_type": "text/plain", "text_content": "Content of doc three." }, "file_type": "wiki", "path_preview": "https://wiki.company.com/doc-003", "last_modified": "2026-04-09T13:00:00Z" } ] }' ``` ```json 200 theme={null} { "status": "ok", "documents_received": 2, "message": "2 sent, 0 already indexed, 0 skipped, 0 failed.", "results": [ { "document_id": "doc-002", "outcome": "sent", "message": "document sent to the indexing pipeline." }, { "document_id": "doc-003", "outcome": "sent", "message": "document sent to the indexing pipeline." } ] } ``` ## Document fields For the full field reference (types, required/optional, descriptions) see [Datasource & properties --- Standard document fields](/indexing-api/setup-datasource#standard-document-fields). A few field-level reminders: * **`file_type`** is required and must be one of the supported values listed under [File types](/indexing-api/setup-datasource#file-types) (or fetched from `GET /file-types`). * **`data_source_sub` / `data_source_sub_sub`** must be **slug keys** that match a sub declared on the datasource (`word_word` form, e.g. `engineering`, `runbooks`). amber builds the compound storage values (`{datasource}__{sub_key}` etc.) for you. * **`author`** is a free-form **string** (an email or display name); it is not an object. * **`path`** is a breadcrumb string for display (e.g. `Engineering / Runbooks / Onboarding`). For the URL to open the document, use `path_preview`. ### Content extraction (two options) | Approach | What you send | Who extracts searchable text | | ------------------------ | ----------------------------- | ------------------------------------------------------------ | | **Server processing** | `binary_base64` + `mime_type` | **amber** processes the file and derives text for the index. | | **Client-supplied text** | `text_content` | **You** supply the content to be indexed. | Send **either** `binary_base64` **or** `text_content` in `body`, never both. **Server processing** --- Encode the file with standard Base64. Set `mime_type` to the real media type for that file. There is no closed list of supported types; use the correct [IANA media type](https://www.iana.org/assignments/media-types/media-types.xhtml) for whatever you upload. **Client-supplied text** --- Pass `text_content` with the content you want indexed. ```python theme={null} import base64 with open("quarterly-report.pdf", "rb") as f: binary_base64 = base64.standard_b64encode(f.read()).decode("ascii") # Server processing: { "mime_type": "application/pdf", "binary_base64": binary_base64 } # Client text: { "mime_type": "text/plain", "text_content": "..." } ``` **Per-document binary cap: 100 MB (decoded).** The cap is enforced by the downstream indexing service — the public `/api/indexing` proxy does **not** decode `binary_base64` itself, so an oversize payload reaches upstream and is rejected there; the resulting error is forwarded verbatim. The default is controlled by `indexing_api_max_binary_bytes`; ask your operator if you need it raised. ### Custom properties on documents If your datasource has [property definitions](/indexing-api/setup-datasource#custom-attributes-property-definitions), send matching values in `custom_properties` on each document. Each entry is a `{ "name", "value" }` pair where `name` matches a property definition key. ```bash theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/indexing/documents" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "documents": { "datasource": "internal_wiki", "id": "onboarding", "title": "Onboarding", "body": { "mime_type": "text/plain", "text_content": "..." }, "file_type": "wiki", "path_preview": "https://wiki.company.com/onboarding", "last_modified": "2026-03-25T10:00:00Z", "custom_properties": [ { "name": "department", "value": "Engineering" }, { "name": "ticket_id", "value": "WIKI-42" } ], "permissions": { "allow_anonymous_access": true } } }' ``` Values are validated against the datasource schema --- the property `name` must exist in the definitions, and the `value` must match the declared `property_type`. See [Schema validation & errors](#schema-validation--errors) for details. ## Updating a document To update an existing document, simply re-index it with the same `id`. The entire document is replaced with the new payload: The pipeline re-indexes a document only when **`last_modified` has changed** or its **access tokens have changed** (any change to the `permissions` block — `allow_anonymous_access`, `allowed_users`, or `allowed_groups`). Edits to other fields (e.g. `title`, `body`, `custom_properties`) without bumping `last_modified` or changing permissions are treated as no-ops, and the response reports `outcome: "already_indexed"`. Bump `last_modified` whenever you want content edits to take effect. ```bash theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/indexing/documents" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "documents": { "datasource": "internal_wiki", "id": "wiki-getting-started", "title": "Getting Started with the Internal Wiki (Updated)", "body": { "mime_type": "text/plain", "text_content": "Updated content for the getting started guide..." }, "file_type": "wiki", "path_preview": "https://wiki.company.com/getting-started", "last_modified": "2026-05-01T09:00:00Z", "permissions": { "allow_anonymous_access": true } } }' ``` If neither `last_modified` nor the access tokens have changed, nothing is re-queued and the response reports `outcome: "already_indexed"`. ## Deleting a document ```bash theme={null} curl -X DELETE "https://customerDomain.ambersearch.de/api/indexing/documents/internal_wiki/wiki-getting-started" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **Response codes for `DELETE /documents/{datasource}/{document_id}`** | Code | When | | ----------------- | --------------------------------------------------------------------------------------------------------- | | **200 OK** | Delete-by-id issued to Solr — idempotent, so this is returned even if no document with that `id` existed. | | **404 Not Found** | Datasource not found. | Indexing a document does **not** make it immediately searchable. The server controls when new and updated documents are committed to the search index. This can take **up to 2 hours** depending on system load and commit scheduling. Do not rely on instant availability after a successful `POST`. ## Schema validation & errors Every document is validated against the datasource's schema (object definitions, property definitions, **and** declared sub keys) **before** it is persisted. If validation fails the API returns **HTTP 422** with this envelope: ```json theme={null} { "status": "error", "details": { "error_code": "UNKNOWN_PROPERTY", "message": "Property 'priority' is not defined on datasource 'internal_wiki'. Valid properties: ['department', 'ticket_id']." } } ``` Pydantic field-level errors (e.g. missing required fields, invalid `id` characters) use a different shape — the standard FastAPI `HTTPValidationError`, wrapped in `{"detail": [...]}` with one entry per offending field. The `error_code` envelope above is only used for the service-layer schema-validation cases listed below. ### Error codes | Error code | When it is returned | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `NO_PROPERTY_DEFINITIONS` | The datasource has no property definitions, but the document includes `custom_properties`. Define properties on the datasource first. | | `UNKNOWN_PROPERTY` | A `custom_properties` entry has a `name` that does not match any `property_definitions` on the datasource. | | `INVALID_PROPERTY_TYPE` | The value of a custom property does not match the declared `property_type`. For example, sending a string for an `INTEGER` property or a non-boolean for a `BOOLEAN` property. | | `INVALID_DATA_SOURCE_SUB` | `data_source_sub` (or `data_source_sub_sub` when no subs are declared at all) does not match a top-level sub key of the datasource. | | `INVALID_DATA_SOURCE_SUB_SUB` | `data_source_sub_sub` is not a child of the supplied `data_source_sub`, or is unknown across the whole datasource when sent without a parent. | | `AMBIGUOUS_DATA_SOURCE_SUB_SUB` | `data_source_sub_sub` was sent without `data_source_sub`, but the same child key exists under multiple parent subs. Set `data_source_sub` explicitly. | ### Expected value types | `property_type` | Accepted JSON values | | --------------- | ----------------------------------------- | | `TEXT` | string | | `INTEGER` | number (integer or float) | | `DATE` | number (epoch seconds) or ISO 8601 string | | `BOOLEAN` | `true` / `false` | In **bulk** requests, validation runs **per document** and failures are reported in `results` with `outcome: "failed"` and a `message` describing the cause. The other documents in the same batch are still processed. ### Envelope summary | Source | Envelope shape | | ---------------------------------------------------- | ---------------------------------------------------------------------------------- | | FastAPI `HTTPException` (most 400 / 404 / 409 / 503) | `{"detail": ""}` | | Pydantic body / path validation (422) | `{"detail": [{"loc": [...], "msg": "...", "type": "..."}]}` | | Service-layer schema validation (422) | `{"status": "error", "details": {"error_code": "", "message": ""}}` | For 401 / 422-Pydantic / 500 responses that can come from any endpoint, see [Common HTTP responses](/indexing-api/introduction#common-http-responses) on the overview page. ## Next steps Configure fine-grained access controls for your documents. Adjust object types, sub-datasources, and property schemas on the datasource. # Indexing API Overview Source: https://developers.ambersearch.de/indexing-api/introduction Push custom content into amber to make it searchable alongside your other data sources. ## Overview The amber Indexing API enables your organization to push content from internal tools, on-premises systems, and proprietary applications into amber's search index. Documents pushed through this API become discoverable alongside content from all your natively connected data sources. The Indexing API is designed for a **push-based** integration model. Instead of amber pulling data from your system, your application pushes documents (one at a time or in batches) directly to amber via HTTP API calls. The public `/api/indexing` endpoints are a thin **proxy** layer in front of amber's internal indexing service. Each request you send is forwarded to the downstream service, which performs the actual extraction and indexing work. HTTP status codes and error envelopes are preserved verbatim, so the responses you observe come directly from the indexing pipeline. ## Browse this guide These pages are not listed in the site sidebar. Use the cards below (or the [recommended order](#recommended-reading-order) at the end) to move between topics. This page --- API overview, auth, quick examples Create datasource, sub-datasources, object types, property schemas Single + bulk indexing, fields, update, delete, error handling Users, groups, anonymous access, check-access ## Common Use Cases * **Internal Tool Integration** --- Make content from proprietary or on-premises tools searchable in amber. * **Legacy System Modernization** --- Bring content from older systems into modern search and AI workflows. * **Custom Application Data** --- Index structured data from internal applications and databases. * **Document Repositories** --- Make file servers, wikis, and document management systems searchable. * **Ticketing & CRM Data** --- Push tickets, cases, and customer data for unified search. ## How It Works Register the datasource and declare object types, custom property definitions, and the optional sub-datasource hierarchy in one call. See [Datasource & custom properties](/indexing-api/setup-datasource). Push documents with `POST /documents` --- send a single document or an array under `documents`. For each one you choose between **file binaries** (server-side extraction) and **clean `text_content`** you supply yourself. Updates and deletes use the same endpoint family. See [Index documents](/indexing-api/index-documents). Set access controls so users only see content they are authorized to view. Documents without an explicit `permissions` block are treated as anonymous and visible to everyone **registered on that datasource** (`POST /users`) --- not to your whole org. A user with no record on the datasource sees none of its documents. See [Permissions](/indexing-api/permissions). Documents become discoverable through amber's search and AI features once the server commits them to the index. This is controlled server-side and can take **up to 2 hours**. ## Base URL All Indexing API endpoints are available under: ``` https://customerDomain.ambersearch.de/api/indexing ``` ## Authentication You need the **Administrator** and **Developer** roles in amber to create an indexing service account. The Indexing API authenticates with a **service-account API key** sent as a Bearer token: ```http theme={null} Authorization: Bearer amsa_ ``` Service-account tokens are created by an administrator from the **Service Accounts** page in the amber dashboard UI. Each token begins with the `amsa_` prefix so leaked credentials are easy to spot in logs. The secret is shown **only once** at creation time — store it in your secret manager immediately, then use it in place of `YOUR_API_KEY` in the examples on these pages. ```bash theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/indexing/datasources" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` ### Linking people who search to your indexed data Who can see a document in amber is evaluated against **the same person who signs in to amber**. The **required** way to connect that logged-in identity to the Indexing API is the user's **email attribute** --- the email on their amber account (as provided by your identity provider / directory and stored on the user in amber). * When you index users, set **`user.email`** to that **exact** login email. * In permissions and group memberships, refer to people **by that email** (`allowed_users`, `member_email` for memberships, and checks that take `user_email`). Do not rely on arbitrary external IDs alone to represent "who is logged in"; amber resolves search visibility using the email tied to the amber user. ## Naming rules at a glance A few identifiers must be **slugs** in `word_word` form (lowercase letters/digits separated by single underscores --- no hyphens, no spaces, no leading digit): * **Datasource `name`** (e.g. `internal_wiki`) * **Sub `key`** (e.g. `engineering`, `runbooks`) Group names cannot contain whitespace and cannot start with `amber` (case-insensitive). Document `id`s are free-form alphanumeric with hyphens, underscores, and dots. ## Quick Example Here's how to create a datasource and index your first document: **1. Create a datasource:** ```bash theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/indexing/datasources" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "internal_docs", "display_name": "Internal Documentation" }' ``` **2. Index a document:** ```bash theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/indexing/documents" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "documents": { "datasource": "internal_docs", "id": "getting-started-guide", "title": "Getting Started Guide", "body": { "mime_type": "text/plain", "text_content": "This guide helps new employees get up to speed quickly..." }, "file_type": "page", "path_preview": "https://internal.company.com/docs/getting-started", "last_modified": "2026-05-01T09:00:00Z", "permissions": { "allow_anonymous_access": true } } }' ``` The same endpoint accepts an **array** under `documents` for bulk indexing — a separate bulk route is not needed. See [Index documents](/indexing-api/index-documents#index-multiple-documents). ## Common HTTP responses These responses can come back from **any** endpoint and are not repeated in the per-endpoint tables on the other pages. | Code | When it occurs | | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **401 Unauthorized** | The `Authorization: Bearer …` header is missing or the service-account token is invalid / expired. | | **422 Unprocessable Entity** *(Pydantic envelope)* | Request body or path failed Pydantic validation — missing required field, regex mismatch (e.g. `name` not in `word_word` slug form), wrong type, `max_length` exceeded, `body` with both `text_content` and `binary_base64`, decoded `binary_base64` over 100 MB, or `MembershipDefinition` without exactly one of `member_email` / `member_group_name`. Response shape: `{"detail": [{"loc": [...], "msg": "...", "type": "..."}]}`. | | **500 Internal Server Error** | Unhandled server error (DB outage, etc.). Safe to retry. | Service-layer validation errors (custom-property and sub-reference schema violations) use a **different envelope** with an `error_code` field — see [Schema validation & errors](/indexing-api/index-documents#schema-validation-errors) on the Index documents page. ## Limits & timing The Indexing API does **not** apply a per-minute request quota. The only hard limit enforced today is the **per-document binary payload size**: `binary_base64` bodies are capped at **100 MB (decoded)** by default. The cap is enforced by the downstream indexing service (the public `/api/indexing` proxy does **not** decode binaries itself), so an oversize payload is rejected upstream and the error is forwarded verbatim. The default is controlled by the `indexing_api_max_binary_bytes` setting — ask your operator if you need it raised. Indexed content is not immediately searchable. The server commits new data to the search index on its own schedule, which can take up to 2 hours. ## Recommended reading order 1. [**Datasource & custom properties**](/indexing-api/setup-datasource) --- create the datasource, sub-datasource tree, object types, and property definitions. 2. [**Index documents**](/indexing-api/index-documents) --- push and update documents (single or bulk), bodies, and per-document permissions. 3. [**Permissions**](/indexing-api/permissions) --- users, groups, memberships, and access checks. ## Next Steps Datasource, sub-datasources, object definitions, property schemas, and sending values on documents. Index, update, and delete documents (single or bulk). Configure fine-grained access controls for your indexed documents. # Example: Odoo Integration Source: https://developers.ambersearch.de/indexing-api/odoo-integration A complete, runnable example of pushing Odoo records into amber via the Indexing API. Browse the full source, setup instructions, and a runnable end-to-end sample that indexes Odoo records through the amber Indexing API. This example walks through a real-world integration: reading records from [Odoo](https://www.odoo.com/) and pushing them into amber using the [Indexing API](/indexing-api/introduction). Use it as a starting point for your own connector. # Permissions Source: https://developers.ambersearch.de/indexing-api/permissions Control who can see indexed documents using the permissions block on each document. **Indexing API (BETA)** --- [Overview](/indexing-api/introduction) · [Datasource & properties](/indexing-api/setup-datasource) · [Index documents](/indexing-api/index-documents) · [Permissions](/indexing-api/permissions) ## Overview Set `permissions` on each document when you [index it](/indexing-api/index-documents). Search only returns documents the signed-in user is allowed to see. **Access is always scoped to the datasource first.** A signed-in user sees a datasource's documents **only if they are registered as a user on that datasource** (`POST /users` --- see [Specific users only](#2-specific-users-only)). This applies to **anonymous documents too**: `allow_anonymous_access: true` (and documents indexed with no `permissions` block) are visible to every registered user of *that* datasource, but **not** to org members who have no user record on it. Register yourself before expecting to see your own indexed content. **User context uses login email.** The person searching is always an amber user identified by the **email on their account** (the email they have when they log in to amber). Every permission that targets a specific person must use **that same email**: `POST /users` → `user.email`, `allowed_users` → `{ "email": "..." }`, `POST /memberships` → `member_email`, and `POST .../documents/check-access` → `user_email`. Email lookups are **case-insensitive** (stored lowercased). Pick **one** primary rule per document: | Goal | Set | | -------------------------------------------------- | ---------------------------------------------------------------------- | | Everyone registered on this datasource can find it | `allow_anonymous_access: true`, **or** omit `permissions` entirely | | Only listed people | `allowed_users` (after indexing those users --- see below) | | Members of named groups | `allowed_groups` (after creating groups and memberships --- see below) | Documents indexed **without a `permissions` block** are stored with the anonymous token. That token reaches only users who are **registered on this datasource** (created with `POST /users`) --- not everyone in your amber org. A signed-in user with no user record on the datasource sees **none** of its documents, including anonymous ones. To restrict further *within* the datasource, send `allowed_users` and/or `allowed_groups` on the document. ## 1. Open to everyone registered on the datasource ```json theme={null} "permissions": { "allow_anonymous_access": true } ``` "Anonymous" here means **no per-user or per-group restriction inside the datasource** --- it does **not** mean public to your whole org. The document is still only reachable by people registered on the datasource via `POST /users`. ## 2. Specific users only Permissions are always resolved by **email** --- the same address on the user's amber account. **a)** Register each person once per datasource. **`email` is required** and must equal the address on their amber user profile (same as when they log in). The optional `name` is for display only and does **not** replace email for access checks. ```bash theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/indexing/users" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "datasource": "internal_wiki", "user": { "email": "alice@company.com", "name": "Alice Smith" } }' ``` **Response codes for `POST /users`** | Code | When | | ------------------- | -------------------------------------------------------------------------- | | **200 OK** | User created. | | **400 Bad Request** | `user.email` is missing or empty after normalization (trim + lower-case). | | **404 Not Found** | Datasource not found. | | **409 Conflict** | A user with the same (normalized) email already exists in this datasource. | Update a user's display name later with `PUT /users/{datasource}/{email}`: ```bash theme={null} curl -X PUT "https://customerDomain.ambersearch.de/api/indexing/users/internal_wiki/alice@company.com" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Alice S." }' ``` **Response codes for `PUT /users/{datasource}/{email}`** | Code | When | | ------------------- | ------------------------------------------------------------------- | | **200 OK** | User updated. | | **400 Bad Request** | The `email` path parameter is empty after normalization. | | **404 Not Found** | Datasource not found, or no user with that email in the datasource. | **b)** Reference them on the document with **`allowed_users` and the same emails**: ```json theme={null} "permissions": { "allowed_users": [ { "email": "alice@company.com" }, { "email": "bob@company.com" } ] } ``` ## 3. Groups Order of operations: 1. **Users** --- `POST /users` for each person (same as above). 2. **Group** --- `POST /groups` with a `name`. **Group name rules**: must be non-empty, must **not contain whitespace**, and must **not start with `amber`** (case-insensitive). 3. **Membership** --- `POST /memberships` with `group_name` and **either `member_email` (the member's amber login email) or `member_group_name` (to nest another group)**. Exactly one of the two must be set. 4. **Document** --- `allowed_groups: ["engineering"]`. Create a group: ```bash theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/indexing/groups" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "datasource": "internal_wiki", "group": { "name": "engineering" } }' ``` **Response codes for `POST /groups`** | Code | When | | ------------------- | --------------------------------------------------------------------------- | | **200 OK** | Group created. | | **400 Bad Request** | `group.name` contains whitespace or starts with `amber` (case-insensitive). | | **404 Not Found** | Datasource not found. | | **409 Conflict** | A group with that name already exists in this datasource. | Rename it later with `PUT /groups/{datasource}/{group_name}`: ```bash theme={null} curl -X PUT "https://customerDomain.ambersearch.de/api/indexing/groups/internal_wiki/engineering" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "group_name": "eng-team" }' ``` **Response codes for `PUT /groups/{datasource}/{group_name}`** | Code | When | | ------------------- | ------------------------------------------------------------------- | | **200 OK** | Group renamed. | | **400 Bad Request** | The new `group_name` contains whitespace or starts with `amber`. | | **404 Not Found** | Datasource not found, or no group with that name in the datasource. | Add a user as a member (`member_email` = amber login email): ```bash theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/indexing/memberships" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "datasource": "internal_wiki", "membership": { "group_name": "engineering", "member_email": "alice@company.com" } }' ``` Or nest one group inside another (`member_group_name`): ```bash theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/indexing/memberships" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "datasource": "internal_wiki", "membership": { "group_name": "all-staff", "member_group_name": "engineering" } }' ``` **Response codes for `POST /memberships`** | Code | When | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | **200 OK** | Membership created. | | **404 Not Found** | Datasource not found, parent group not found, or the referenced user / `member_group_name` does not exist. | | **409 Conflict** | The membership already exists, **or** the new `group → group` edge would close a cycle — the response message lists the offending path. | Document snippet: ```json theme={null} "permissions": { "allowed_groups": ["engineering"] } ``` ### Remove a membership `DELETE /memberships/{datasource}/{group_name}/{member_type}/{member_id}` --- `member_type` is `user` or `group`. For `user`, `member_id` is the email; for `group`, it is the nested group's name. ```bash theme={null} curl -X DELETE "https://customerDomain.ambersearch.de/api/indexing/memberships/internal_wiki/engineering/user/alice@company.com" \ -H "Authorization: Bearer YOUR_API_KEY" curl -X DELETE "https://customerDomain.ambersearch.de/api/indexing/memberships/internal_wiki/all-staff/group/engineering" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **Response codes for `DELETE /memberships/{datasource}/{group_name}/{member_type}/{member_id}`** | Code | When | | ------------------- | ----------------------------------------------------------------------------------------------------- | | **200 OK** | Membership removed. | | **400 Bad Request** | `member_type` is neither `user` nor `group`. | | **404 Not Found** | Datasource, parent group, referenced user / member-group, or the membership row itself was not found. | Group membership is **transitive**. When `engineering` is a member of `all-staff`, anyone who is a member of `engineering` (directly or via further nesting) is also treated as a member of `all-staff` for access checks. ## Verify access `user_email` must be the same **amber login email** you use in `/users` and permissions. The check resolves the user's transitive group memberships and compares them to the document's stored access tokens. ```bash theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/indexing/documents/check-access" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "datasource": "internal_wiki", "document_id": "architecture-doc", "user_email": "alice@company.com" }' ``` ```json 200 theme={null} { "has_access": true, "datasource": "internal_wiki", "document_id": "architecture-doc", "user_email": "alice@company.com" } ``` **Response codes for `POST /documents/check-access`** | Code | When | | ---------------------------- | ----------------------------------------------------------------- | | **200 OK** | Access decision returned in `has_access`. | | **404 Not Found** | Datasource not found, or Solr has no document with that `id`. | | **422 Unprocessable Entity** | `user_email` is missing or empty (rejected by the request model). | Remember that newly indexed documents may take **up to 2 hours** to be committed and become discoverable; `check-access` queries the search index directly so it will not find a document that is still being processed. ## Behaviour notes * Permission updates can take a short time to apply everywhere in search. * Tighten rules after testing: start with `allow_anonymous_access: true` (or no `permissions`) if you need to confirm indexing before locking down. Even then, register your own login email on the datasource with `POST /users` first --- without a user record you will not see the datasource's documents, anonymous or otherwise. * Renaming a group **after** documents reference it will leave existing Solr documents pointing at the old name; re-index those documents to apply the new name. # Datasource & custom properties Source: https://developers.ambersearch.de/indexing-api/setup-datasource Create a custom datasource, declare object types, property schemas, and the sub-datasource hierarchy used to filter results. **Indexing API (BETA)** --- [Overview](/indexing-api/introduction) · [Datasource & properties](/indexing-api/setup-datasource) · [Index documents](/indexing-api/index-documents) · [Permissions](/indexing-api/permissions) ## Overview You do two things, in order: 1. **Create a datasource** --- declare **object types** (e.g. `wiki_page`, `account`) plus their **property definitions** (custom attributes such as `department`, `priority`), and optionally a tree of **sub-datasources** (used as the first/second-level filters in the search UI). Everything goes in a single `POST /datasources` call. 2. **Index documents** --- send the standard built-in fields plus a `custom_properties` array whose `name` values match the property definitions, and (optionally) reference a sub-datasource by its `key`. Every document carries a set of **standard (built-in) fields** that amber already knows how to index, display, and filter. On top of those you can define **custom attributes** (property definitions) for metadata specific to your datasource. Define properties and subs **before** you index documents that reference them. Adding or changing definitions later can delay or skew search behavior until affected documents are re-indexed (for example by re-sending them with `POST .../documents`). ## Naming rules (slugs) Several identifiers in the Indexing API must be **slugs** in `word_word` form: * **Datasource `name`** --- e.g. `internal_wiki` * **Sub `key`** --- e.g. `engineering`, `runbooks` The slug regex is `^[a-z][a-z0-9]*(_[a-z][a-z0-9]*)*$`: * lowercase letters and digits only, * words separated by **single underscores** (no hyphens, no spaces, no leading digit). For subs, the `key` is **auto-generated from `name`** if you omit it (e.g. `"HR Documents"` → `hr_documents`). The datasource `name` must always be supplied explicitly. ### Standard document fields These fields are available on every document without any property definition. They use the same naming conventions as all amber datasources: | Field | Type | Required | Description | | --------------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `datasource` | string | Yes | `name` of the target datasource. | | `id` | string | Yes | Unique document identifier within the datasource. Alphanumeric, hyphens, underscores, dots. | | `title` | string | Yes | Document title shown in search results. | | `body` | object | Yes | Searchable content --- either file binary (`binary_base64` + `mime_type`) or text (`text_content`). See [Content extraction](/indexing-api/index-documents#content-extraction-two-options). | | `file_type` | enum | Yes | Semantic type of the document. See [File types](#file-types). | | `path_preview` | string | Yes | URL to open the document in its original application. | | `last_modified` | string | Yes | ISO 8601 datetime when the document was last modified. Used for change detection. | | `path` | string | No | Breadcrumb path inside the source system (e.g. `Engineering / Runbooks / Onboarding`). | | `author` | string | No | Free-form author identifier (e.g. an email or display name). | | `data_source_sub` | string | No | First-level sub-datasource **key** (must match a sub declared on the datasource). See [Sub-datasource hierarchy](#sub-datasource-hierarchy). | | `data_source_sub_sub` | string | No | Second-level sub-datasource **key** (child of `data_source_sub`). | | `permissions` | object | No | Access control. If omitted, the document is anonymous --- visible to everyone **registered on this datasource** (`POST /users`), not to your whole org. See [Permissions](/indexing-api/permissions). | | `custom_properties` | array | No | Your custom attributes. Each entry has a `name` (matching a [property definition](#custom-attributes-property-definitions)) and a `value`. | For indexing examples, updates, deletes, and error handling see [Index documents](/indexing-api/index-documents). ### File types `file_type` is **required** on every document and tells amber how to render and treat the content. It must be one of: `message`, `merge_request`, `issue`, `wiki`, `markdown`, `blog`, `content`, `timeline`, `email`, `file`, `directory`, `email_parent`, `issue_parent`, `wiki_parent`, `page`, `merge_request_parent`, `webpage`, `asset`. Pick the value that best matches the source object (e.g. `wiki` for a wiki page, `file` for an arbitrary uploaded file, `webpage` for a scraped page, `email` for a mail message). The list is also exposed at runtime: ```bash theme={null} curl -X GET "https://customerDomain.ambersearch.de/api/indexing/file-types" \ -H "Authorization: Bearer YOUR_API_KEY" ``` `GET /file-types` always returns **200 OK**. ### Sub-datasource hierarchy amber uses a three-level filter hierarchy for every datasource. These levels let users progressively narrow results: | Level | Field on the document | Source | Example UI label | | ----------- | --------------------- | ---------------------- | ---------------------------------- | | **Level 0** | `datasource` | The datasource `name`. | "Internal Wiki" (= `display_name`) | | **Level 1** | `data_source_sub` | A top-level sub `key`. | "Engineering" (= sub `name`) | | **Level 2** | `data_source_sub_sub` | A child sub `key`. | "Runbooks" (= sub `name`) | Subs are declared on the datasource (during creation, or later via the [sub endpoints](#manage-subs-after-creation)). On documents you reference subs **by their `key`** (the slug); amber derives the human-readable label from the sub's `name` and the compound storage values automatically. If a `data_source_sub_sub` key is **unique** across the datasource, you can send it on a document **without** `data_source_sub` --- amber will infer the parent. If the same child key appears under multiple parents the request fails with `AMBIGUOUS_DATA_SOURCE_SUB_SUB`; set `data_source_sub` explicitly in that case. ### Custom attributes (property definitions) For metadata that goes beyond the built-in fields, you declare **property definitions** inside an **object definition** on the datasource and send matching **`custom_properties`** on each document. Each property definition requires: * **`name`** --- the **attribute key** you reference later in `custom_properties` when indexing documents. * **`display_label`** --- the **attribute display name** shown in the search UI. You also choose whether values should be **searchable** (`is_searchable`): when `true`, the property values are included in the full-text index so users can find documents by typing those values in a free-text query --- not only via structured filters. ## Create the datasource (with subs and property schemas) `POST /api/indexing/datasources` --- include `subs` for the sub-datasource tree and `object_definitions` (with nested `property_definitions`) so amber knows which metadata fields exist for each object type. ```bash cURL theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/indexing/datasources" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "internal_wiki", "display_name": "Internal Wiki", "subs": [ { "name": "Engineering", "children": [ { "name": "Runbooks" }, { "name": "Architecture", "key": "architecture" } ] }, { "key": "design", "name": "Design", "children": [{ "name": "Tokens" }] } ], "object_definitions": [ { "name": "wiki_page", "display_label": "Wiki Page", "property_definitions": [ { "name": "department", "display_label": "Department", "property_type": "TEXT", "is_searchable": true }, { "name": "ticket_id", "display_label": "Ticket ID", "property_type": "TEXT", "is_searchable": true } ] } ] }' ``` ```python Python theme={null} import requests requests.post( "https://customerDomain.ambersearch.de/api/indexing/datasources", headers={ "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, json={ "name": "internal_wiki", "display_name": "Internal Wiki", "subs": [ { "name": "Engineering", "children": [ {"name": "Runbooks"}, {"name": "Architecture", "key": "architecture"}, ], }, { "key": "design", "name": "Design", "children": [{"name": "Tokens"}], }, ], "object_definitions": [ { "name": "wiki_page", "display_label": "Wiki Page", "property_definitions": [ { "name": "department", "display_label": "Department", "property_type": "TEXT", "is_searchable": True, }, { "name": "ticket_id", "display_label": "Ticket ID", "property_type": "TEXT", "is_searchable": True, }, ], } ], }, ).raise_for_status() ``` **Response codes** | Code | When | | ---------------- | --------------------------------------------------------------------------------------- | | **201 Created** | Datasource created (with the inline `subs` tree and `object_definitions`, if provided). | | **409 Conflict** | A datasource with the same `name` already exists. | In the search UI filters this renders as: ``` Internal Wiki (Level 0 - datasource) ├── Engineering (Level 1 - data_source_sub: "engineering") │ ├── Runbooks (Level 2 - data_source_sub_sub: "runbooks") │ └── Architecture (Level 2 - data_source_sub_sub: "architecture") └── Design (Level 1 - data_source_sub: "design") └── Tokens (Level 2 - data_source_sub_sub: "tokens") ``` ### Datasource fields | Field | Required | Description | | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | Yes | Unique slug (`word_word`, max 128 chars). Lowercase letters and digits separated by single underscores. | | `display_name` | Yes | Label shown in the search UI (max 255 chars). | | `icon_base64` | No | Base64-encoded icon image for the datasource (e.g. PNG, SVG). | | `subs` | No | Initial sub-datasource tree. Each entry is a [`SubDefinition`](#sub-definition-fields). Children may be nested via `children` (max 2 levels: a top-level sub and one level of `children`). | | `object_definitions` | No | List of object types. Each has a `name`, optional `display_label`, and a list of [`property_definitions`](#property-definition-fields). | ### Sub definition fields | Field | Required | Description | | ------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | Yes | Display name shown in the search UI (max 512 chars). Unique per parent. | | `key` | No | Machine-readable slug used on documents (`word_word`). Auto-generated from `name` if omitted (e.g. `"HR Documents"` → `hr_documents`). Unique per parent. | | `parent_key` | No | Key of the parent sub. Use this when adding a child sub via `POST .../subs` after creation. | | `children` | No | Nested children (only used when **creating** a datasource with an initial tree). | Subs are limited to **two levels**: a top-level sub and one level of children. A child sub cannot have its own children. ### Property definition fields | Field | Required | Description | | --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | Yes | **Attribute key** --- the identifier you use later in `custom_properties` on documents. | | `display_label` | Yes | **Attribute display name** --- human-readable label shown in the search UI. | | `property_type` | No | `TEXT` (default), `INTEGER`, `DATE`, `BOOLEAN`. | | `is_searchable` | No | `false` (default). When `true`, property values are included in the full-text index so documents are findable by free-text queries on those values --- not only via structured filters. | **When to set `is_searchable: true`** --- Use it for values users might type in a search box, such as ticket IDs, project codes, or category names. Leave it `false` for purely structural metadata (e.g. internal database IDs or numeric scores) that should only be available as structured filters. ### Property types | Type | Description | | --------- | ---------------------------------------------- | | `TEXT` | String value. | | `INTEGER` | Numeric value. | | `DATE` | Timestamp (use epoch **seconds** or ISO 8601). | | `BOOLEAN` | `true` / `false`. | Once your property definitions are in place, attach values on each document using `custom_properties` when [indexing](/indexing-api/index-documents#custom-properties-on-documents). ## Manage subs after creation `POST /api/indexing/datasources/{name}/subs` adds a single sub to an existing datasource. **Add a top-level sub** (auto-slug from `name`): ```bash theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/indexing/datasources/internal_wiki/subs" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Legal Documents" }' ``` **Add a child sub** by setting `parent_key` to an existing top-level sub key: ```bash theme={null} curl -X POST "https://customerDomain.ambersearch.de/api/indexing/datasources/internal_wiki/subs" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Invoices", "parent_key": "finance" }' ``` **Response codes for `POST /datasources/{name}/subs`** | Code | When | | ----------------- | ---------------------------------------------------------------------------------- | | **201 Created** | Sub created. | | **404 Not Found** | Datasource not found, or the supplied `parent_key` does not match an existing sub. | | **409 Conflict** | A sub with the same `key` or `name` already exists under the same parent. | **Remove a sub** by its `key`. Children of a deleted sub are removed as well (cascade). ```bash theme={null} curl -X DELETE "https://customerDomain.ambersearch.de/api/indexing/datasources/internal_wiki/subs/finance" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **Response codes for `DELETE /datasources/{name}/subs/{sub_key}`** | Code | When | | ------------------ | -------------------------------------------------------------------------- | | **204 No Content** | Sub deleted (children cascade). | | **404 Not Found** | Datasource not found, or no sub with that `key` exists for the datasource. | Deleting a sub does not delete documents that reference it; those documents will remain in the index but lose the sub-level filter binding until you re-index them with a valid `data_source_sub` / `data_source_sub_sub`. ## List, get, update, or delete a datasource **List** ```bash theme={null} curl -X GET "https://customerDomain.ambersearch.de/api/indexing/datasources" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **Get one** (returns the datasource and its full sub tree) ```bash theme={null} curl -X GET "https://customerDomain.ambersearch.de/api/indexing/datasources/internal_wiki" \ -H "Authorization: Bearer YOUR_API_KEY" ``` **Update** --- `PUT /api/indexing/datasources/{name}`. Only `display_name`, `icon_base64`, and `object_definitions` can be changed here. **Subs must be added or removed via the [sub endpoints](#manage-subs-after-creation).** ```bash theme={null} curl -X PUT "https://customerDomain.ambersearch.de/api/indexing/datasources/internal_wiki" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "display_name": "Internal Wiki (renamed)" }' ``` **Delete** --- removes the datasource together with its subs, users, groups, memberships, and indexed documents. Cascade-deletes the datasource and all related data (subs, users, groups, memberships, and indexed Solr documents). Cannot be undone. ```bash theme={null} curl -X DELETE "https://customerDomain.ambersearch.de/api/indexing/datasources/internal_wiki" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response codes | Endpoint | Code | When | | ---------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `GET /datasources` | **200 OK** | Always (returns an empty list if none exist). | | `GET /datasources/{name}` | **200 OK** | Datasource found. | | | **404 Not Found** | No datasource with that `name`. | | `PUT /datasources/{name}` | **200 OK** | Updated. | | | **404 Not Found** | Datasource not found. | | `DELETE /datasources/{name}` | **204 No Content** | Datasource and all its Solr documents purged. | | | **404 Not Found** | Datasource not found. | | | **503 Service Unavailable** | The Solr purge step failed; the DB record was left intact — the operation is idempotent, so retry the request to finish the cascade. | ## Next step Push and update documents and permissions. # Introduction Source: https://developers.ambersearch.de/mcp/introduction This document provides an overview of the amber MCP Server, its purpose, and how to get started. Model Context Protocol (MCP) is a standardized way to add extra context to LLMs via external data sources and tools. Simply put, with using various MCP servers, you can have your LLM interact with your GitLab, search the web, find places on a map, and more. MCP servers are designed to be easy as possible to set up and use, so you can integrate them into your applications quickly. ## Getting Started We have set up the MCP server to be accessible through a remote URL, so you can start using it right away, without needing to install anything locally. The server is available at: ``` https://customerDomain.ambersearch.de/mcp/ ``` ## Authentication The authentication for the MCP server is the same as for the public REST API. You will need to provide your API key or a jwt through the OAuth 2 flow in the `Authorization` header of your requests with the `Bearer` scheme. In addition to the normal OAuth2 flow where the client credentials get passed, the OAuth 2 flow for the MCP server includes discovery endpoints for the authorization server as referred to in the authorization manual. ## MCP Client Integration ### Step 1: OAuth discovery In order to initiate an authorization flow it is required to discover its OAuth configuration. Given the MCP server URL (e.g., [https://customerDomain.ambersearch.de/mcp](https://customerDomain.ambersearch.de/mcp)), use a standard two-step discovery process: 1. Based on [RFC 9470](https://datatracker.ietf.org/doc/html/rfc9470): Fetch Protected Resource Metadata to find which authorization server(s) protect this resource 2. After retrieving the protected resource metadata the authorization server metadata endpoint can be extracted, based on [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414) The Protected Resource Metadata tells you where to find the authorization server, and the Authorization Server Metadata tells you the specific OAuth endpoints to use. Thus an initial handshake is required to get the Protected Resource Metadata by hitting the `/mcp` endpoint, the expected result from this handshake is a status code 401 and a WWW-Authenticate HTTP header with the resource metadata, refer to [this](https://modelcontextprotocol.io/specification/draft/basic/authorization#protected-resource-metadata-discovery-requirements). ```http theme={null} GET /mcp ``` **Response:** ```json theme={null} { "error": "invalid_token", "error_description": "Authentication failed. The provided bearer token is invalid, expired, or no longer recognized by the server. To resolve: clear authentication tokens in your MCP client and reconnect. Your client should automatically re-register and obtain new tokens." } ``` **Response Headers:** ```json theme={null} { "Content-Type": "application/json" "Connection": "keep-alive" "WWW-Authenticate": "Bearer error="invalid_token", error_description="Authentication failed. The provided bearer token is invalid, expired, or no longer recognized by the server. To resolve: clear authentication tokens in your MCP client and reconnect. Your client should automatically re-register and obtain new tokens.", resource_metadata="https://customerDomain.ambersearch.de/.well-known/oauth-protected-resource/mcp" ... } ``` ```http theme={null} GET /.well-known/oauth-protected-resource/mcp Accept: application/json ``` **Response:** ```json theme={null} { "resource": "https://customerDomain.ambersearch.de/mcp", "authorization_servers": [ "https://customerDomain.ambersearch.de/" ], "scopes_supported": [], "bearer_methods_supported": [ "header" ], "resource_name": "amber MCP Server" } ``` ### Step 2: Initiate the OAuth 2 flow After retrieving the authorization server metadata from the authorization server metadata endpoint, the client can initiate the authorization flow as referenced in the amber OAuth 2 manual with either `PKCE` or `client_secret`. ## Configuration Most of the applications use the same configuration format for handling MCP servers. Here's a sample configuration that you can use to set up your MCP client to connect to the amber MCP server: ```json theme={null} { "mcpServers": { "amber": { "transport": "http", "url": "https://customerDomain.ambersearch.de/mcp/", "headers": { "Authorization": "Bearer ambrs-exampletoken" } } } } ``` ```json theme={null} { "mcpServers": { "amber": { "transport": "http", "url": "https://customerDomain.ambersearch.de/mcp/", "headers": { "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } } } } ``` ## Tools The amber MCP server provides the following tools: | Tool Name | Description | | ----------------------- | ------------------------------------------------------------ | | `search` | Search for data across multiple data sources. | | `web_search` | Perform a web search using specified queries and parameters. | | `get_document_metadata` | Retrieve metadata for a specific document. | Detailed descriptions of the tools and their parameters are as follows: * **search** - Search for data across multiple data sources. * `query`: The search term to look for (string, required) * `count`: The number of results to return (number, optional) * `data_source`: The data source to search in (string, optional) * `file_type`: The type of files to search for (string, optional) * `last_modified_date`: The time frame for the last modified date (string, optional) * `start_page`: The page number to start from (number, optional) * **web\_search** - Perform a web search using the provided queries and parameters. * `queries`: A list of search queries (array of strings, required) * `count`: The number of results to return (number, optional) * `sites`: A list of specific sites to search within (array of strings, optional) * `timeout`: The maximum time to wait for the search results in seconds (number, optional) * **get\_document\_metadata** - Retrieve metadata for a specific document. * `document_id`: The unique identifier of the document (string, required) * `provide_text_attribute`: Whether to include the document's text content in the response (boolean, optional)