PlatformXeDocs
Get API Key

Product AI & Vision

How the PlatformXe AI gateway and vision service work — AI profiles, feature keys, limits, daily quotas, tool calling, knowledge retrieval, error codes and idempotency.

PlatformXe runs your product's AI traffic through one gateway, so policy, limits and spend are controlled in one place. Your app sends prompts; PlatformXe decides what each feature may do.

Scope

PlatformXe is an infrastructure API. The AI & Vision gateway is one more capability on the same API as messaging, storage and OCR, and it is deliberately narrow:

  • Your app owns the prompt, the context and the decision. PlatformXe never decides for the caller, never executes a tool call (it returns the call; your code runs it) and never acts on a result.
  • One provider-neutral contract: complete, extract, analyze-room and knowledge search. The provider and model behind a profile — Gemini, Grok or Claude today — are profile settings Caldera changes without a client change.
  • Zero data retention on every request: the provider keeps neither prompt nor output; PlatformXe keeps a usage row (tokens, cost, feature) and a short-lived audit detail that the retention job purges.
  • Metered in naira: the gateway's cost for every call is recorded against your feature, and each profile's daily caps stop a runaway feature.
  • Out of scope: training or hosting models, autonomous agents, and decisions taken on your behalf. PlatformXe routes, meters and polices your calls; it is not an AI product.
EndpointPurposeScopeRate tier
POST /api/v1/ai/completeOne chat turn (Gemini, Grok or Claude)ai:complete600/hr per key
POST /api/v1/ai/extractPrompts (and images) in, schema-validated JSON outai:complete600/hr per key
GET /api/v1/ai/profile?featureKey=Your profile for one feature + today's usageai:complete600/hr per key
GET /api/v1/ai/usage?from=&to=&featureKey=Your AI and vision usageai:complete600/hr per key
POST /api/v1/vision/analyze-roomProperty photo → room category + alt textvision:analyze2,000/hr per key
PUT / DELETE / GET /api/v1/ai/knowledge/documentsIndex, delete and list your knowledge documentsai:knowledge1,200/hr per key
POST /api/v1/ai/knowledge/searchHybrid search over your documents, with citationsai:knowledge1,200/hr per key

SDK reference: /sdk/ai and /sdk/vision. Machine-readable contract: the AI, AI Knowledge and Vision tags of the OpenAPI document.

AI profiles

Every call runs under an AI profile, selected by two values:

  • the caller service — taken from your API key, never from the request body, so one app cannot borrow another app's profile;
  • featureKey — names the product use case (guest-concierge, room-vision, ai-canvas, document.freeform …).

A profile is set up by Caldera for your service. It holds:

SettingEffect
statusACTIVE or DISABLED. A disabled profile refuses with 403 AI_PROFILE_DISABLED; no profile at all is 403 AI_PROFILE_NOT_FOUND.
allowedProviders / allowedModelsWhich providers and models the feature may use (empty models list = the provider defaults).
Policy textAppended after the PlatformXe baseline policy. You own your prompts; PlatformXe owns the policy. Withheld from the profile view.
maxTokens, maxMessages, maxTotalCharsPer-call limits (see below).
allowImagesWhether extract accepts images.
allowToolsWhether complete accepts tools and tool-calling turns (see Tool calling).
dailyTokenCap, dailyRequestCapDaily caps per UTC day; null = no cap.
Output screening, PII redactionScreen model replies before returning them; redact emails / phone numbers / ID numbers before they reach the model.

GET /api/v1/ai/profile?featureKey=… (client.ai.profile) shows your profile's settings with today's usedTokensToday, usedRequestsToday and resetsAt (the next UTC midnight).

Feature keys

A featureKey is 1–64 lowercase letters, digits, . or -, starting with a letter or digit. It is required on complete and extract; analyze-room defaults to room-vision.

sourceApp is a deprecated alias for featureKey (types 3.5.0; removed in 4.0.0). It is only read when featureKey is absent. Send featureKey.

Limits

LimitValue
Messages per complete turnThe profile's maxMessages, never more than 64
Characters per turn (all message content, or both extract prompts together)The profile's maxTotalChars, never more than 256,000
Each extract prompt32,000 characters
Images per extract4, each a public https url or base64 (≤ 8 MiB decoded, image/jpeg / image/png / image/webp) — only when the profile allows images
analyze-room imageExactly one of imageUrl (public https) / imageBase64 (≤ 8 MiB decoded)
Output tokensThe profile's maxTokens, never more than the deployment ceiling (256–32,768)
extract temperature0–2, default 0.2
Usage windowfrom / to at most 92 days apart; default the last 30 days

A breached limit is 400 BAD_REQUEST.

Daily quotas

A profile's dailyTokenCap and dailyRequestCap are counted per UTC day. They are checked after the safety screen and before the model is called; a call that reaches the provider counts against them.

When a cap is reached the call is refused with 429 AI_QUOTA_EXCEEDED, a Retry-After header with the seconds until the next UTC midnight, and error.details = { metric, retryAfterSeconds }. It is distinct from the per-key rate tier (429 RATE_LIMITED, which resets within the hour). The official SDKs do not retry AI_QUOTA_EXCEEDED.

Usage and cost

Every call that reached a profile is recorded once — succeeded (OK), refused by the safety screen (REFUSED), blocked on output (BLOCKED), over quota (QUOTA_EXCEEDED), degraded (FALLBACK, vision) or failed (FAILED). GET /api/v1/ai/usage (client.ai.usage) returns your own usage grouped by feature, kind (ai / vision / embedding), provider and model, with token counts and cost in PlatformXe credits (1 credit = 1 kobo). Knowledge embeddings — at indexing time and for a search query — are recorded as kind embedding against the profile named by the request's featureKey.

Tool calling

With allowTools on the profile, POST /api/v1/ai/complete accepts tools (up to 16 function tools: name, description, JSON Schema parameters) and toolChoice (auto, none, required or { name }). The reply may carry toolCalls with finishReason: "tool_calls".

PlatformXe is a pipe for tools: it never executes a tool call. Your app runs each call through its own permission checks and audit, then sends the result back as a role: "tool" message (toolCallId) after the assistant message that carried the calls.

Tool traffic gets the same data handling as text: input screening and PII redaction cover tool results and earlier call arguments, output screening covers every returned call's arguments, and redaction placeholders are restored in the arguments you receive. Without allowTools, tools or tool messages are refused with 400 BAD_REQUEST ("tools not enabled for this feature").

Knowledge retrieval

/api/v1/ai/knowledge/* (scope ai:knowledge) indexes your own documents — policies, handbooks, knowledge pages — and searches them, so an assistant can answer from your data with citations.

  • Indexing — PUT /documents with featureKey, collection, your sourceRef, title, optional url and acl, and markdown or plain text (≤ 400,000 characters). The text is split by heading into ~800-token chunks with a 100-token overlap; fenced code is never split. Re-sending the same text only updates the metadata.
  • ACLs — acl holds opaque tags you own. A search sees a document when its aclTags hold any of the document's tags; a document without tags is visible to every search of your service.
  • Search — POST /search with featureKey, collections, query (≤ 1,000 characters), aclTags and k (1–20, default 8). Candidates come from Postgres full-text and trigram (typo-tolerant) matching; when embeddings are available the query is embedded and cosine similarity is fused with the lexical ranks by reciprocal rank fusion (mode: "hybrid"), otherwise mode: "lexical". Every hit carries sourceRef, ordinal and heading for a citation. The query is screened like any AI input.
  • Embeddings — Google text-embedding-005 through the AI gateway with zero data retention. When the gateway is unavailable, documents are still indexed and searchable lexically; embeddings are filled in later.
  • Reconciliation — GET /documents?collection= lists your documents with their contentHash; delete what no longer exists on your side with DELETE /documents?collection=&sourceRef=.

Indexed text is stored exactly as sent: PlatformXe does not redact personal data at index time. Do not index Tier-1 / Tier-2 personal data. Documents are kept until you delete them.

Vision

POST /api/v1/vision/analyze-room classifies a property photo into one of the roomCategories you send (your own taxonomy) and, when wantAltText is true, writes alt text in the requested locale (en-NG, en-GB or en-US). The response carries roomCategory (or null), roomConfidence, altText, detectedFeatures, modelVersion, aiStatus and metrics.

A provider timeout or overload is not an error: the endpoint answers 200 with aiStatus: "FALLBACK" and a deterministic answer, so an upload flow never blocks on it.

Idempotency

analyze-room accepts an optional x-idempotency-key header (scoped to your API key):

SituationResponse
First request with a keyProcessed; the response is stored for 24 h
Same key again, after it finishedThe stored response, with X-Idempotency-Cache: HIT
Same key while the first is still running409 CONFLICT
The answer was a FALLBACKReturned with X-Idempotency-Cache: skip and not stored — retrying the same key later reaches the provider again
The idempotency store is unavailable503 IDEMPOTENCY_UNAVAILABLE with Retry-After — nothing ran, retry is safe

Error responses (400, 401, 403, 404, 422, 429 and 5xx) are never stored.

Error codes

Every error uses the standard envelope { "success": false, "error": { "code", "message", "details"? } }.

CodeStatusEndpointsMeaning
BAD_REQUEST400allMalformed body or query, or a limit breached
UNAUTHORIZED401allMissing, invalid or inactive API key
FORBIDDEN403allThe key lacks ai:complete / ai:knowledge / vision:analyze, or its organization is inactive
AI_PROFILE_NOT_FOUND403complete, extract, profile, analyze-room, knowledge PUT + searchNo profile for your caller service + featureKey
AI_PROFILE_DISABLED403complete, extract, analyze-room, knowledge PUT + searchThe profile is switched off
AI_POLICY_DENIED403complete, extract, knowledge searchThe input was refused by the safety screen
CONFLICT409analyze-roomThe idempotency key is still in flight
AI_OUTPUT_BLOCKED422complete, extractThe model reply was refused by output screening
AI_OUTPUT_INVALID422extractThe reply did not match outputJsonSchema; details.path is the JSON Pointer of the first failing value
RATE_LIMITED429allThe per-key rate tier is spent (Retry-After)
AI_QUOTA_EXCEEDED429complete, extract, analyze-roomThe profile's daily cap is reached (Retry-After to the next UTC midnight; details.metric)
SERVER_ERROR500allUnexpected failure
AI_UPSTREAM_ERROR502complete, extractThe model provider failed or timed out
AI_NOT_CONFIGURED503complete, extractThe AI gateway is not configured on that deployment
VISION_NOT_CONFIGURED503analyze-roomNo vision provider (AI gateway or Azure fallback) is configured on that deployment
IDEMPOTENCY_UNAVAILABLE503analyze-roomThe idempotency store is unavailable — nothing ran

/api/v1/ocr/* now requires its own scope, ocr:run. Keys created with the default scopes carry ai:complete, vision:analyze and ocr:run for the services that use them; an older key with an explicit scope list needs them granted before it can call these routes.