This is the main webservice of the Full Stack mode. It receives the visitor's message, internally orchestrates the AI, the semantic search over the customer's inventory, and builds the ready-to-display response. It saves the visitor's message and the AI's response in the conversation (there's no need to call another endpoint to persist them).
This endpoint consumes AI tokens from the customer's account (customer_ai_config). Before invoking it you can check the available balance with GET /api/action/mapaprop-chat-ai-v1/validate.
| Resource information | |
|---|---|
| Authentication | Required (API Token, Bearer) |
| Scope | mapaprop-chat-ai |
| HTTP Method | POST |
| Response | JSON |
| Version | 1 |
Resource URL
https://mapaprop.app/api/action/mapaprop-chat-ai-v1/chat
Parameters
The request body is a JSON. customerId and websiteId are not sent: they are derived from the authentication token.
| Key | Type | Required | Description |
|---|---|---|---|
| message | string | yes | The visitor's message. |
| questionId | int | no | The ID of the conversation to continue. If omitted, a new conversation is created and the assigned questionId comes in the response (questionId field). Resend it on every following turn. You can also get it from a previous call to POST /init. |
| leadName | string | no | Visitor's name. Default Visitante. If the conversation already has an associated contact and it's not sent, the existing one is kept. |
| leadEmail | string | no | Visitor's email. If sent and the conversation doesn't have an associated contact yet, a contact is created or linked in the customer's CRM. |
| leadPhone | string | no | Visitor's phone number. |
| sessionId | string | no | Visitor's session identifier (for traceability). If not sent, a UUID is generated automatically. |
| mode | string | no | chat (default), search or semantic. See "Search mode" below. |
| propertyIds | array of {id: int} objects | no | IDs of pre-filtered properties (for example, obtained from a previous search on your side). The model decides whether the message is a factual query ("which one is more expensive?") or a semantic one ("which one has a pool?") over this set. |
| useVectorSearch | boolean | no | Forces a semantic (vector) search over the customer's properties to build the response context. Automatically enabled if propertyIds or mode=semantic is sent. |
| searchCriteria | object | no | Classic search filter (operation, type, zone, priceMin, priceMax, currency) used only when mode=search and propertyIds was not sent. Legacy mode: propertyIds or useVectorSearch is recommended. |
| propertyActive | object {propertyId: int, ...} | no | Indicates that the conversation is about ONE specific property (the visitor is viewing its listing page). Takes priority over any other search mode. |
| searchContext | object | no | Context of the guided flow (operation / type / zone already chosen by the visitor via buttons). Advanced use, reserved for reproducing the official widget's flow. |
| customerSearch | object | no | Data from the visitor's previous searches/favorites/inquiries, used to enrich the contact created in the CRM. Advanced use. |
| sourceUrl | string | no | URL of the page where the visitor is chatting. Saved in the conversation. |
Search mode
- If you send
propertyIds, the AI responds based on that set (with semantic re-ranking if you also senduseVectorSearch=true). - If you send
useVectorSearch=true(ormode=semantic) withoutpropertyIds, a semantic search is run over the customer's entire inventory. - If you send
propertyActive, the response focuses on that single property (takes priority over everything else). - Without any of the above, it's a free-form conversation (
chatwith no search context).
Sample code
POST /api/action/mapaprop-chat-ai-v1/chat HTTP/1.1
Host: mapaprop.app
Content-Type: application/json
Authorization: Bearer {access_token}
{
"message": "¿Tenés departamentos de 2 ambientes en Palermo?",
"questionId": 481203,
"leadName": "Juan Pérez",
"leadEmail": "juan@example.com",
"sessionId": "b6d1c6b0-2f2a-4e2e-9a4a-8e0a2a2f2a11",
"useVectorSearch": true
}
Response
The response includes the conversation's questionId (the assigned one if it was new, or the one you sent). Save it and resend it on every following turn to keep the thread going. It also returns the period's token consumption (totalTokensUsed / tokensRemaining).
| Object | Field | Type | Required | Description |
|---|---|---|---|---|
| Response | response | string | yes | The AI's response text, ready to display. |
| questionId | int | yes | Conversation ID (the assigned one if it was new, or the one you sent). Resend it on the next turn. | |
| action | string | no | Action suggested by the model. Possible values: greeting, show_properties, confirm_search, request_info, contact. May come empty. | |
| success | boolean | yes | false when the turn couldn't be processed (see "Handled errors" below). | |
| propertyIds | array of int | no | IDs of the properties referenced in this response (only present in search mode). | |
| buttons | array of objects | no | Suggested buttons for the visitor (only present in some flows). | |
| allPropertyIds | array of int | no | All the IDs of the search result (before trimming to the ones shown). Search mode only. | |
| hasMoreResults | boolean | yes | true if there are more search results than those shown in propertyIds. | |
| focusedPropertyId | int | no | ID of the property in focus (single property mode). | |
| focusedPropertyCode | string | no | Code of the property in focus. | |
| searchCriteria | object | no | Search criteria interpreted by the model (search mode). | |
| tokensUsed | object | yes | Detail of tokens consumed in this turn: {input, output, total}. | |
| totalTokensUsed | int | no | AI tokens consumed by the account in the current period (after this turn). | |
| tokensRemaining | int | no | AI tokens remaining in the current period. | |
| error | string | no | Only present when success == false. See table below. |
Handled errors (success: false, HTTP 200)
These scenarios do not throw an exception: the response arrives with success: false, a response in Spanish ready to show the visitor, and an error with the reason.
| Error | Cause |
|---|---|
TOKEN_LIMIT_EXCEEDED | The customer reached their plan's monthly AI token limit. |
INSUFFICIENT_TOKENS | There aren't enough tokens left in the current period to process this specific message. |
CONVERSATION_LIMIT_EXCEEDED | This conversation (questionId) already consumed the maximum tokens allowed per conversation. A new conversation must be started. |
USER_MESSAGE_TOO_LONG | The message sent exceeds the maximum allowed length (~300 tokens, ~225 words). |
| (message in Spanish, not a short code) | Internal failure of the AI engine (provider down, parsing error, etc.). The error field in this case contains the same text as response, not a short code. |
Validation errors (the request is cut off, success: false is not returned)
| Error | Cause |
|---|---|
REQUIRED_INPUT — "Message is required" | The message field is missing from the body. |
Sample response
{
"response": "Sí, tenemos 3 departamentos de 2 ambientes en Palermo. Te comparto los más relevantes.",
"questionId": 481203,
"action": "show_properties",
"success": true,
"propertyIds": [2601234, 2601987, 2602111],
"hasMoreResults": false,
"tokensUsed": {
"input": 812,
"output": 143,
"total": 955
},
"totalTokensUsed": 18420,
"tokensRemaining": 81580
}
Example of a handled error (period token limit):
{
"response": "Lo siento, se ha alcanzado el limite de uso mensual del chat. El limite se reiniciara el 2026-09-01.",
"action": "",
"success": false,
"hasMoreResults": false,
"tokensUsed": {
"input": 0,
"output": 0,
"total": 0
},
"error": "TOKEN_LIMIT_EXCEEDED"
}