API reference
Use this reference when writing a client or changing a component boundary. Classic orchestration, hosted execution, ingestion, and panel APIs are different contracts. A bearer accepted by one surface does not automatically authorize another.
For identity setup, use Sign-in & access. For settings, schedules, administration, and recovery, use Application settings, Jobs & schedules, Administration, and Monitoring & audit. Examples below illustrate requests, not authorization to run them against production. Do not put keys or tokens in source, browser code, or logs.
Select the surface
| Surface | Service | Authentication boundary |
|---|---|---|
/orchestrator, /conversations |
Classic orchestrator | Service credential, plus a separate user bearer where required |
/responses, /invocations |
Foundry-hosted orchestrator | Foundry ingress authentication and configured delegated retrieval or Toolbox context |
/document-chunking, /text-embedding, /ingest-documents |
Ingestion | X-API-KEY, not a delegated-user replacement |
/retrieve |
Ingestion preview | Separately gated delegated user bearer |
/api/dashboard/* |
Classic orchestrator dashboard | Conditional mounting and dashboard Admin gate |
/api/*, /api/panel/* |
Ingestion administration | Mode-specific mounting and distinct admin gates |
/panel/conversations/* |
UI backend | Delegated bearer, enabled history/continuity, and owner-index check |
/panel/overview/metrics, /panel/corpus-curation/* |
Ingestion | Explicit operator role/group and feature gates |
JSON errors commonly contain detail, but ingestion skill endpoints also
return record errors and some plain-text 400 responses. Do not impose one
error parser on all routes. An error after a streaming response has started
must be read from the stream, even if the HTTP status was 200.
Classic orchestrator
Authentication
The service-auth dependency checks these credentials in priority order:
X-Service-Authorization: Bearer <service-access-token>, validated against the configured tenant,ORCHESTRATOR_AUTH_AUDIENCE, andORCHESTRATOR_ALLOWED_CALLER_IDS.dapr-api-token, validated against the server'sAPP_API_TOKENorDAPR_API_TOKEN.X-API-KEY, validated againstORCHESTRATOR_APP_APIKEY.
An invalid higher-priority credential fails rather than falling through.
Missing or invalid service credentials return 401.
Authorization: Bearer <user-api-token> is a separate user boundary.
ALLOW_ANONYMOUS defaults to true for the ask route, not for bypassing
service auth or granting access to history.
Use DISABLE_AUTH=true only in controlled private development.
The user token must target the configured API, not Microsoft Graph.
The backend derives identity and groups from validated context.
Deprecated client_principal_id, client_principal_name,
client_group_names, and identity-like user_context fields do not establish
an authenticated user.
Ask and stream
POST /orchestrator, Content-Type: application/json:
{
"ask": "Summarize the document retention policy.",
"conversation_id": "an-existing-conversation-id",
"question_id": "q-123",
"user_context": {}
}
Omit conversation_id for a new conversation. question is a compatibility
alias for ask. A non-feedback request without either returns 400.
Model/schema violations return 422. User authentication failures return
401, and authorization failures can return 403.
The response has Content-Type: text/event-stream and X-Correlation-ID.
Server-sent events, or SSE, normally frame incremental output with event/data
lines. The classic wire format is a legacy text stream: it starts with
the conversation ID followed by a space, then emits text chunks, citations,
and tool progress. Normal chunks are not JSON data: events.
Preserve this distinction when implementing a client.
An in-stream error is framed as:
event: error
data: An internal server error occurred.
Network chunk boundaries are not semantic message boundaries. Do not treat emitted text as proof that a background Cosmos write completed. Persistence and feedback behavior are described in Conversations & files.
Feedback
The same POST /orchestrator accepts feedback without ask:
{
"type": "feedback",
"conversation_id": "an-existing-conversation-id",
"question_id": "q-123",
"is_positive": true,
"stars_rating": 5,
"feedback_text": "The answer was useful."
}
conversation_id is required for this operation. The rating is optional and,
when present, must be 1..5. A missing conversation ID returns 400.
Success is JSON, not a stream:
{"status": "success", "message": "Feedback saved successfully"}
User conversation routes
All routes below require service authentication and an authenticated user.
Anonymous ask eligibility does not authorize them. Detail and mutation routes
check the authenticated owner. Missing conversations return 404, denied
ownership can return 403, and persistence failures return 500.
| Request | Contract |
|---|---|
GET /conversations?skip=0&limit=10&name=policy |
Optional name filter. Returns conversations, has_more, skip, limit. Each row has id, name, created_at, and last_updated. |
GET /conversations/{conversation_id} |
Returns id, name, principal_id, created_at, last_updated, and messages. Do not assume every strategy stores a full transcript in Cosmos. |
PATCH /conversations/{conversation_id} |
Body {"name":"Retention policy"}. Returns id, name, last_updated. Empty names fail schema validation or return 400 after trimming. |
DELETE /conversations/{conversation_id} |
Soft delete. Returns {"status":"success","message":"Conversation deleted successfully"}. It does not erase every downstream artifact. |
Hosted orchestrator
The hosted runtime is stateless across turns. It does not create, read, append, or delete managed Foundry Conversations. The authenticated client supplies ordered history on every request. An opaque conversation label scopes tagging and conversation retrieval, not ownership of a stored transcript.
Foundry authenticates ingress. Do not expose the container directly and assume
it implements the classic service-auth dependency.
With native protected retrieval, the configured UI obtains downstream user
tokens through on-behalf-of exchange. The raw Search token travels separately
as x-client-search-token. Work IQ can additionally require the original
validated UI API assertion as x-client-work-iq-assertion, not a Search token.
These are raw credential values, not Bearer-prefixed strings or trusted
principal claims.
For hosted mcp, a valid platform-injected x-agent-foundry-call-id is
mandatory, including on response-management routes. Missing or malformed
context returns 401. A caller cannot replace it with claimed groups,
metadata, a static key, or service identity.
Only maf_lite, maf_agent_service, single_agent_rag, and mcp are
hosted-eligible. Unsupported strategies fail rather than falling back.
For the non-Toolbox protected retrieval path, missing/malformed required
credentials return 401, credentials over 8,192 UTF-8 bytes return 431,
and missing required conversation scope returns 422.
Unsupported authorization mode or incomplete source configuration can return
503. Generic Foundry IQ MCP sources are rejected with 503 in this protected
native path until a verified source-specific delegation contract exists.
That gate is separate from the hosted mcp strategy.
Responses
POST /responses is the canonical hosted protocol:
{
"input": [
{"role": "user", "content": "What is the retention period?"},
{"role": "assistant", "content": "It depends on the record category."},
{"role": "user", "content": "Explain the policy for invoices."}
],
"stream": true,
"store": false,
"metadata": {
"conversation_id": "an-opaque-conversation-label",
"question_id": "q-124"
}
}
input can also be one nonblank string. A message array must be nonempty,
ordered, and end with the current nonblank user ask.
Supported roles are user, assistant, system, and developer.
Content is text, either a string or input_text/output_text parts with
text strings. Images and other multimodal parts are rejected.
Metadata contains only string keys and values.
The adapter always forces store=false, including when the caller omits it
or sends true. Non-null previous_response_id and background=true return
422. The adapter ignores routing/sampling fields it does not honor, including
top-level conversation, model, and session_id. They cannot select stored
state or replace the server's agent definition.
Malformed JSON returns 400. Invalid input contracts return 422.
Retrieval/context guards can reject the request before streaming.
With stream=true, parse named SSE events and their JSON data payloads.
The lifecycle includes response.created, response.in_progress, output-item
and content-part events, response.output_text.delta, and terminal
response.completed or response.failed.
The terminal object is not a classic text stream.
The runtime does not emit internal citation/tool-progress events as fabricated
Responses annotation or function-call objects.
Invocations
POST /invocations?api-version=v1 is the compatibility protocol used by the
shipped UI hosted client:
{
"messages": [
{"role": "user", "content": "What is the retention period?"},
{"role": "assistant", "content": "It depends on the record category."},
{"role": "user", "content": "Explain the policy for invoices."}
],
"conversation_id": "an-opaque-conversation-label",
"metadata": {"question_id": "q-124"}
}
messages requires at least one entry. Roles are only user and assistant,
and content is a string. The final message must be a nonblank user ask.
Omit conversation_id to let the runtime generate a label.
Metadata is an optional object. Caller-provided identity is not trusted.
Malformed JSON returns 400 and model/final-message violations return 422.
Unsupported strategy selection returns 422.
The response is named SSE with JSON data and an X-Response-ID header.
response.created contains a response object with conversation.id.
The UI also recognizes the legacy conversation_id field.
Missing, malformed, or conflicting identifiers fail the UI client explicitly.
Text arrives in response.output_text.delta events. Error events and failed
terminal events must not be presented as complete answers.
By default, the UI sends conversation_id only after the runtime has issued
one. It resends available Chainlit history without a local item/token cap and
adds the current ask if needed. A Chainlit thread ID is not a hosted
conversation ID.
When optional continuity is enabled, the UI owns separate Conversations store
calls. The hosted invocation then omits a conversation reference.
See Conversations & files for store ownership, one-page
history limits, and failure semantics. Do not send a Responses input body
to /invocations or use previous_response_id as continuity.
Conversations store used by the UI
These are downstream store operations, not additional routes implemented by the hosted orchestrator:
| Store request | UI client contract |
|---|---|
POST /conversations |
Sends {}, requires a nonempty id in the response |
GET /conversations/{id}/items?order=asc&limit=100 |
Expects a data array of message items. The requested limit is clamped to 1..100, and the client does not follow pagination. |
POST /conversations/{id}/items |
Sends {"items":[...]} containing the turn's message items |
DELETE /conversations/{id} |
Deletes the managed conversation, separately from panel metadata cleanup |
Store authentication is independent of hosted invocation authentication.
The default delegated owner binding uses a service bearer with a
server-derived x-ms-user-identity header and requires the validated
agent-scoped impersonation grant. Explicit capability binding uses the
user's OBO bearer and a signed owner handle in the UI flow, not that header.
Never accept a browser-supplied x-ms-user-identity.
Store read/append failures are explicit failures, not empty history or a
durable-write acknowledgement. Setup, limits, and ownership validation remain
in Conversations & files.
Protected-source stream errors and probes
Both protocols can report authentication_required, interaction_required,
and source_denied. These are non-retryable by automatic model/tool replay.
Only a bounded, validated claims_challenge on interaction_required is
eligible for the UI's explicit sign-in recovery. Do not render provider error
bodies or follow source-supplied authorization URLs.
Use Sign-in & access for recovery.
GET /readiness and compatibility GET /health return status, immutable
image version, and eligible_strategies. They report the image contract,
not successful live model, Search, or source authorization.
Ingestion document APIs
All three document endpoints require X-API-KEY.
The validator accepts DATA_INGEST_APP_APIKEY and legacy
INGESTION_APP_APIKEY. Invalid supplied keys return 401. The shipped
API-key security dependency rejects a missing header with 403.
Credential-based Azure SDK access does not make these HTTP routes keyless.
These are JSON record envelopes, not multipart uploads.
Record errors are arrays of objects such as {"message":"..."}.
Inspect them even on HTTP 200.
Chunk a source document
POST /document-chunking:
{
"values": [{
"recordId": "document-1",
"data": {
"documentUrl": "https://<storage-account>.blob.core.windows.net/<container>/policy.pdf",
"documentContentType": "application/pdf"
}
}]
}
values must be nonempty. Each item requires string recordId and
data.documentUrl/data.documentContentType.
documentSasToken is an optional schema field, not a replacement for the
Blob client's configured access. The handler downloads the document through
that client. If multiple records are supplied, only the last is processed.
Use one record per request.
Success returns {"values":[{"recordId":"document-1","data":{"chunks":[]},
"errors":[],"warnings":[]}]} with actual chunks in data.chunks.
Malformed JSON, schema errors, and missing values return 400.
Chunking failures can be represented as record errors.
Generate embeddings
An embedding is a numeric vector representing text for similarity search.
POST /text-embedding:
{"values":[{"recordId":"text-1","data":{"text":"Retention policy summary"}}]}
The response uses values[] with recordId, data.embedding, errors,
and warnings. A failed record has an error and may have empty data.
Invalid JSON or a missing values field returns 400.
This handler does not share the strict schema of /ingest-documents.
Ingest conversation documents
POST /ingest-documents chunks, embeds, and uploads records to Search:
{
"conversationId": "an-authorized-conversation-id",
"securityUserIds": ["<authenticated-uploader-object-id>"],
"values": [{
"recordId": "attachment-1",
"data": {
"fileName": "note.txt",
"contentType": "text/plain",
"fileBase64": "SGVsbG8="
}
}]
}
conversationId is nonempty. values contains 1..5 records with nonempty
recordId, fileName, contentType, and fileBase64.
The schema rejects unknown fields at the envelope, record, and data levels.
securityUserIds is optional in the wire schema. The trusted caller must
derive any uploader ACL from authenticated identity, not user-entered IDs.
An API key alone does not establish user ownership.
The UI checks its upload gate, user, file, and conversation before dispatch.
Response example:
{"values":[{"recordId":"attachment-1","indexedChunks":1,"errors":[],"warnings":[]}]}
indexedChunks counts Search-confirmed uploads, not attempted chunks.
Validation failures return 400. Chunking, embedding, partial Search writes,
and optional original-Blob persistence can produce per-record errors/warnings.
An HTTP 200 with zero confirmed chunks is not successful indexing.
Retrieval preview
POST /retrieve is separately gated by both HOSTED_RETRIEVAL_ENABLED and
HOSTED_RETRIEVAL_INV_002_VALIDATED, default false.
It returns 503 until both are enabled. The validation setting is an operator
attestation of isolated authorization testing, not an automatic readiness test.
{"query":"invoice retention policy","top":5}
Only query and optional top are accepted. Query text is trimmed, nonblank,
and 1..1000 characters. top is 1..10, default 5.
Identity, groups, and index selection are server-owned, not request fields.
Send a delegated user bearer for the configured
HOSTED_RETRIEVAL_TOKEN_AUDIENCE. Toolbox must pass the user's token through.
The service uses its own Search credential for transport and forwards the
user token unchanged in Search's x-ms-query-source-authorization.
Neither a static key nor managed identity alone replaces that user token.
The response is {"results":[...],"count":n}. Each result has id,
content, and optional title, url, category, source, and score.
It excludes ACL/vector fields and caps content at 8,000 characters.
Missing/invalid bearer returns 401, non-delegated/app-only identity 403,
schema/bounds violations 422, missing configuration 500, and sanitized
Search failures 502. Cleanup failure can still fail an otherwise completed
query.
Orchestrator administration
ENABLE_DASHBOARD=false by default. When enabled, /api/dashboard/* mounts
and /dashboard is served if built assets exist.
With Entra configured, protected APIs require a valid API bearer with exact
Admin role. Missing/invalid bearer returns 401, missing role 403, and
a tenant without client ID 500. Without a tenant, the development gate is
open. Do not expose that configuration publicly.
/version and /auth-config are bootstrap exceptions.
Request under /api/dashboard |
Contract |
|---|---|
GET /version, GET /auth-config |
Image version and browser tenant/client/authority/scope bootstrap, respectively |
GET /overview?days=30 |
Counts and daily series. Optional paired from/to select a bounded date range. Invalid dates/ranges return 400. |
GET /conversations?skip=0&limit=50&search=policy |
Cross-user metadata, limit 1..200, returns conversations, has_more, skip, limit |
GET /conversations/{id} |
Conversation detail, reconstructed user turns, thread reference, and feedback. Missing ID returns 404. This is not a full Foundry transcript proxy. |
GET /config |
Returns label and grouped sections of allowlisted settings |
PUT /config |
Body {"settings":[{"key":"AGENT_STRATEGY","value":"maf_lite"}]} |
POST /config/refresh |
Rebuilds the local cache, returns status:"refreshed" and detail |
POST /config/apply |
Refreshes the local cache, returns status:"applied" and detail, not a container restart |
Configuration validation errors return 422 and per-key errors inside
detail. Write failures return 500. Writes are sequential, not transactional.
An error does not roll back earlier writes. Read back effective values because
write labels and provider precedence can differ.
Ingestion administration
Classic mode mounts /api/* administration and available dashboard assets.
Hosted mode mounts neither classic administration nor /api/panel/* unless
the administrative panel is selected. Mounting is resolved at startup.
Jobs and document APIs are not disabled by hiding the panel.
The classic admin reads do not all require Entra. The require_admin
dependency used for job/config mutations permits development access when no
tenant is configured. This is not the strict hosted-panel or operator gate.
In particular, the shipped file-unblock route does not carry an unconditional
Admin dependency. Protect the whole service boundary deliberately.
| Request | Contract |
|---|---|
GET /api/version, GET /api/identity |
Version and browser authentication/role context |
GET /api/jobs, GET /api/files |
page default 1, pageSize 1..100 default 20, search, sortField, sortOrder (asc/desc), indexerType. Files also accept blocked. Returns items, total, page, pageSize, indexerTypes. |
GET /api/jobs/queue |
Registered job state, scheduled time, in-flight run, and latest-run information |
POST /api/jobs/{job_type}/run |
No body. 202 returns jobType, triggerId, status:"queued". Invalid type format 400, unknown type 404, conflicting run 409, enqueue failure 500. Queued is not completed. |
POST /api/files/unblock?blobName=<encoded-log-path> |
No body. Resets a blocked file-log blob. Requires a path containing /files/ and ending .json. Invalid path 400, missing log 404, read/write failure can return 500. |
GET /api/config |
Curated sections, flat settings, canEdit, authEnabled |
PUT /api/config |
Body {"updates":[{"key":"RUN_JOBS_ON_STARTUP","value":false}]} |
POST /api/config/reload |
Refreshes local caches, returns {"status":"ok"} |
POST /api/config/apply |
Refreshes and reapplies schedules. Returns status, rescheduled, note, not a hard container restart. Schedule failures return 500 with failed/rescheduled keys. |
Configuration updates return applied, failed, and rescheduled.
All-success is 200, partial success 207, all-invalid/denied entries 422,
and all-write failure 500. Confirmed writes remain applied if best-effort
cache refresh fails. Schedule or write errors do not reverse earlier effects.
Use Application settings for allowlists and refresh.
Use the job types reported by the running service, not an invented job name.
Hosted administrative panel
In ingestion hosted/panel mode, /api/panel/status exposes nonsensitive
mode, panelEnabled, and authEnabled bootstrap metadata.
Protected /api/panel/* routes require a validated bearer and exact Admin
role, with no tenant-absent development bypass.
| Request | Contract |
|---|---|
GET /api/panel/overview |
Ingestion jobs/files and feedback aggregates |
GET /api/panel/feedback?conversationId=<id> |
Optional conversation filter, returns a list of feedback records |
POST /api/panel/feedback |
Strict body shown below, returns a record with generated id, createdBy, and createdAt, status 201 |
GET /api/panel/conversations/{conversation_id}/history |
Returns 501. Ingestion does not retrieve managed chat message bodies. |
{
"conversationId": "an-existing-conversation-id",
"messageId": "message-1",
"rating": "up",
"comment": "Useful grounding.",
"tags": ["reviewed"]
}
conversationId is required and bounded to 256 characters.
rating is up or down. Optional messageId is at most 256 characters,
comment 4,000, and tags at most 16 strings of at most 64 characters.
Unknown fields fail validation. Invalid input returns 422, denied role
403, and Cosmos failures or invalid stored records return sanitized 502.
This admin feedback shape is not the UI owner-feedback contract below.
UI conversation panel
The UI's /panel/conversations routes are distinct from ingestion
administration. They return 503 unless administrative panel, panel history,
and hosted continuity are active. They require a delegated bearer and derive
the user from it. Every per-conversation operation checks the Cosmos owner
index before touching the managed Conversations store.
An unknown or nonowned ID returns opaque 404, not another user's metadata.
Missing/invalid bearer returns 401, unauthorized identity 403,
invalid schema/cursor 422, and store failures 502.
| Request | Contract |
|---|---|
GET /panel/conversations?limit=20&cursor=<opaque> |
Limit 1..50, returns items of id, title, created_at, updated_at, plus nullable next_cursor |
GET /panel/conversations/{id}/messages?limit=200 |
Limit 1..200, returns {"items":[{"role":"user","content":"..."}]}. Content is capped at 20,000 characters per item and underlying store pagination limits still apply. |
POST /panel/conversations/{id}/feedback |
Strict owner-feedback body below, returns the record plus created_at |
GET /panel/conversations/{id}/feedback |
Returns {"items":[...]} of owner feedback |
DELETE /panel/conversations/{id} |
Deletes managed conversation first, then feedback/owner metadata. Returns status:"deleted" or status:"partial" with detail if metadata cleanup failed. A store delete failure returns 502 without metadata cleanup. |
{
"feedback_id": "feedback-1",
"message_ref": "message-1",
"rating": 5,
"category": "helpful",
"comment": "Answered my question."
}
feedback_id and message_ref are required, each 1..128 characters.
Optional rating is -5..5, category at most 64 characters, and comment
at most 2,000. Unknown fields are rejected.
Feedback is user-authored feedback, not a place to copy retrieved documents
or the underlying transcript. Cursors are caller-bound and must not be
transferred between users.
Operator metrics and corpus curation
These ingestion routes are registered independently and return 503 unless
DEPLOY_ADMINISTRATIVE_PANEL, PANEL_OPERATOR_SURFACES_ENABLED, an explicit
PANEL_OPERATOR_APP_ROLE or PANEL_OPERATOR_GROUP_ID, and the
DATA_INGEST_APP_APIKEY cursor-signing secret are configured.
They require a validated delegated user with the configured role or group.
App-only tokens return 403.
| Request | Contract |
|---|---|
GET /panel/overview/metrics |
Returns schema_version:1, generated_at, correlation_id, and counts with conversation, feedback, pending-corpus, and decided-corpus counts |
GET /panel/corpus-curation/queue?cursor=<opaque> |
Returns items and nullable next_cursor. Items have item_id, document_id, title, reason_code, submitted_at. |
POST /panel/corpus-curation/{item_id}/decision |
Body {"decision":"approve","note":"Reviewed source"}. Decision is approve, reject, or defer, optional note at most 2,000 characters. Returns item_id, decision, decided_at. |
Small metric buckets below PANEL_OVERVIEW_MIN_CARDINALITY, default 5,
are suppressed as null, not zero. Curation operates on blocked-file logs
in Blob, not chat messages. Signed cursors expire after
PANEL_CURSOR_TTL_SECONDS, default 600, and are caller-bound.
Missing/invalid bearer returns 401, role/group denial 403, unknown item
404, invalid/conflicting decisions or invalid/expired/cross-user cursor
422, and data-store failures 502.
UI browser and download routes
The UI also owns these browser/session endpoints, not general-purpose substitutes for the service APIs above:
GET /auth/oauth/azure-adandGET /auth/oauth/azure-ad/callbackimplement configured-tenant sign-in.GET /api/auth/recovery?ticket=<encrypted>continues explicit protected-source sign-in using an encrypted, principal/login-bound, short-lived context. It does not replay a model request or download.POST /copilot/auth/bootstrap,POST /copilot/auth/recovery, andPOST /copilot/auth/logoutbelong to the embedding protocol. Preserve its exact-origin and opaque session requirements instead of calling them as backend service auth.GET /api/download/{grant_token}uses a server-issued signed grant plus the current authenticated principal/session. It is not a URL-fetch proxy. Native protected downloads recheck user/source authorization. Invalid grants do not establish access to a container or conversation.
Use Conversations & files for file limits, continuity, and download policy, and Sign-in & access for recovery expiry and no-replay behavior.