Tip: How Does the REST Entrypoint Work in Solace Agent Mesh?
Solace Agent Mesh is an event-driven framework for building distributed ecosystems of collaborative AI agents, powered by the Solace Event Mesh for enterprise-grade agent orchestration. Agents do the thinking. But to reach them programmatically from your applications, scripts, or CI/CD pipelines, you need an API. That’s what the REST Entrypoint provides.
The REST Entrypoint is a plugin that extends the base gateway framework to expose your agent mesh over HTTP. It gives you a clean async API: submit a task, get a task ID, poll for results, and download any files the agents produce. It is built on FastAPI and includes Swagger documentation out of the box.
In this post, we’ll walk through how the REST Entrypoint works, how to install and configure it, the API it exposes, and the key concepts you need to know when integrating with it.
How It Works
The REST Entrypoint runs an HTTP server (FastAPI + Uvicorn) that accepts task submissions and translates them into A2A protocol messages for the agent mesh. The flow is:
- Your application sends a
POSTrequest with a prompt and an optional file - The Entrypoint translates the request into an A2A task and publishes it to the Solace event broker
- Agents process the task and publish results back
- Your application polls for the result using the task ID
- Once complete, you retrieve the response and any artifacts the agents created
The Entrypoint itself makes no LLM calls. It is a transport layer between your HTTP client and the agent mesh.
Installation
Install the REST Entrypoint plugin with a single command:
sam plugin add my-http-rest --plugin sam-rest-gateway
This creates a configuration file at configs/gateways/my-http-rest.yaml. Before starting, edit the config for your environment.
Important: Authentication is disabled by default in the scaffolded configuration. This is convenient for local development, but make sure you enable authentication before deploying to production (see the Authentication section below).
If you are running the gateway against a local Solace broker, also change the REST API port. The default port (8080) conflicts with the local broker, and the gateway will fail to start with address already in use. Set the port in your .env file:
REST_API_PORT=5050
If you are connecting to a cloud broker, the default port 8080 is fine since there is no local port conflict.
Then start it:
sam run configs/gateways/my-http-rest.yaml
API Reference
The REST Entrypoint exposes two API versions. The v2 API is the recommended path. The v1 API is deprecated and blocks until the task completes or times out.
v2 Endpoints (Recommended)
| Method | Path | Purpose |
|---|---|---|
POST |
/api/v2/tasks |
Submit a task |
GET |
/api/v2/tasks/{taskId} |
Poll for result |
GET |
/api/v2/artifacts/ |
List session artifacts |
GET |
/api/v2/artifacts/{filename} |
Download latest artifact version |
GET |
/api/v2/artifacts/{filename}/versions |
List artifact version numbers |
GET |
/api/v2/artifacts/{filename}/versions/{version} |
Download specific version |
Other Endpoints
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
Health check (no auth required) |
GET |
/api/v2/docs |
Swagger UI for v2 API |
GET |
/api/v1/docs |
Swagger UI for v1 API (legacy) |
POST |
/api/v1/invoke |
Synchronous task submission (deprecated) |
Submitting a Task
Submit a task using multipart/form-data with two required fields: agent_name and prompt. You can optionally attach files.
The examples below assume enforce_authentication: false (dev mode), so no token is needed. If you have authentication enabled, add -H "Authorization: Bearer ${SAM_TOKEN}" to each request (see the Authentication section for how to obtain a token).
Basic request:
curl -s -X POST "http://localhost:5050/api/v2/tasks" \
-F "agent_name=OrchestratorAgent" \
-F "prompt=What is event-driven architecture?"
Response (202 Accepted):
{
"taskId": "abc123-def456"
}
With a file attachment:
curl -s -X POST "http://localhost:5050/api/v2/tasks" \
-F "agent_name=OrchestratorAgent" \
-F "prompt=Summarize the attached document" \
-F "files=@/path/to/document.pdf"
The Entrypoint saves uploaded files to the artifact service and enriches the prompt with file metadata before forwarding to agents.
Polling for Results
After submitting a task, poll the task endpoint until the result is ready:
curl -s -X GET "http://localhost:5050/api/v2/tasks/${TASK_ID}"
- 202 Accepted: The task is still processing. Poll again.
- 200 OK: The task is complete. The response contains the agent’s output.
Completed response example:
{
"id": "task-9f7d5f465f5a4f1ca799e8e5ecb35a43",
"sessionId": "rest-session-36b36eeb69b04da7b67708f90e5512dc",
"contextId": "rest-session-36b36eeb69b04da7b67708f90e5512dc",
"status": {
"state": "completed",
"message": {
"role": "agent",
"parts": [
{ "type": "text", "text": "The agent's response..." }
]
},
"timestamp": "2025-07-03T16:59:37.486480"
},
"artifacts": [],
"metadata": { "agent_name": "OrchestratorAgent" }
}
Important: Results are consume-once. After the first successful
200response, the result is deleted from the cache. Polling again returns202. Design your client to store the response on first retrieval. If your client does not poll within 10 minutes, the result expires and is evicted.
Working with Artifacts
When agents create files (CSV reports, images, documents), they are stored as artifacts scoped to the session. Use the contextId from the poll response to retrieve them.
List artifacts in a session:
curl -s -X GET "http://localhost:5050/api/v2/artifacts/?session_id=${CONTEXT_ID}"
Download an artifact:
curl -s -X GET "http://localhost:5050/api/v2/artifacts/report.csv?session_id=${CONTEXT_ID}" \
-o "report.csv"
Artifacts are versioned. If an agent overwrites a file, previous versions remain accessible via the /versions endpoints.
Full Workflow Example
Here is a complete workflow: submit a task, poll for the result, and download any artifacts the agent creates. This example assumes dev mode (enforce_authentication: false). If authentication is enabled, add -H "Authorization: Bearer ${SAM_TOKEN}" to each curl command (see the Authentication section for how to obtain a token).
Tip: Save this as a script file (e.g.,
test-rest.sh) and run it withbash test-rest.sh. Pasting multi-line scripts directly into the terminal can cause parsing errors.
export SAM_REST_ENTRY="http://localhost:5050"
# 1. Submit task
RESPONSE=$(curl -s -X POST "${SAM_REST_ENTRY}/api/v2/tasks" -F "agent_name=OrchestratorAgent" -F "prompt=Create a CSV with 5 sample products")
TASK_ID=$(echo $RESPONSE | jq -r '.taskId')
echo "Task ID: ${TASK_ID}"
# 2. Poll until complete
while true; do
HTTP_CODE=$(curl -s -o /tmp/poll_result.json -w "%{http_code}" -X GET "${SAM_REST_ENTRY}/api/v2/tasks/${TASK_ID}")
if [ "$HTTP_CODE" = "200" ]; then
echo "Task complete!"
break
fi
echo "Still processing... polling again in 2 seconds"
sleep 2
done
# 3. Show the task result
echo "Result:"
cat /tmp/poll_result.json | jq .
# 4. Get context ID and list artifacts
CONTEXT_ID=$(cat /tmp/poll_result.json | jq -r '.contextId')
echo "Context ID: ${CONTEXT_ID}"
echo "Artifacts:"
curl -s -X GET "${SAM_REST_ENTRY}/api/v2/artifacts/?session_id=${CONTEXT_ID}" | jq .
# 5. Download first artifact (filename is determined by the agent)
FILENAME=$(curl -s -X GET "${SAM_REST_ENTRY}/api/v2/artifacts/?session_id=${CONTEXT_ID}" | jq -r '.[0].filename')
if [ "$FILENAME" != "null" ] && [ -n "$FILENAME" ]; then
curl -s -X GET "${SAM_REST_ENTRY}/api/v2/artifacts/${FILENAME}?session_id=${CONTEXT_ID}" -o "${FILENAME}"
echo "Downloaded ${FILENAME}"
else
echo "No artifacts to download"
fi
Configuration
Here is a typical configuration with the key options:
shared_config:
- broker_connection: &broker_connection
broker_url: ${SOLACE_BROKER_URL, ws://localhost:8008}
broker_username: ${SOLACE_BROKER_USERNAME, default}
broker_password: ${SOLACE_BROKER_PASSWORD, default}
broker_vpn: ${SOLACE_BROKER_VPN, default}
- services:
artifact_service: &default_artifact_service
type: "filesystem"
base_path: "/tmp/samv2"
artifact_scope: namespace
apps:
- name: my_rest_gateway_app
app_module: sam_rest_gateway.app
broker:
<<: *broker_connection
app_config:
namespace: "${NAMESPACE}"
gateway_id: my-rest-gw-01
artifact_service: *default_artifact_service
rest_api_server_host: ${REST_API_HOST, 0.0.0.0}
rest_api_server_port: ${REST_API_PORT, 5050}
enforce_authentication: false
default_user_identity: "sam_dev_user"
default_agent_name: "OrchestratorAgent"
system_purpose: >
The system is an AI assistant with agentic capabilities.
response_format: >
Responses should be clear and concise.
Configuration Options
| Option | Default | Description |
|---|---|---|
rest_api_server_host |
127.0.0.1 |
Host address for the HTTP server |
rest_api_server_port |
8080 |
HTTP port (change to avoid conflicts, e.g., 5050) |
rest_api_https_port |
1943 |
HTTPS port (when SSL certs are provided) |
ssl_keyfile / ssl_certfile |
- | Paths to SSL certificate files for HTTPS |
enforce_authentication |
true |
Require bearer token authentication |
external_auth_service_url |
- | URL of external auth service (required when auth is enforced) |
default_user_identity |
- | Fallback user ID when auth is disabled |
sync_mode_timeout_seconds |
60 |
Timeout for v1 synchronous calls |
gateway_id |
auto-generated | Unique identifier for this Entrypoint instance |
system_purpose |
- | System prompt context sent to agents |
response_format |
- | Instructions for formatting agent responses |
Important: The
gateway_idmust be unique across all Entrypoint instances connected to the same broker. If two instances share agateway_id, responses will be routed unpredictably.
Authentication
The REST Entrypoint has three configuration options that control authentication: enforce_authentication, default_user_identity, and force_user_identity.
enforce_authentication
This is the main switch. It determines whether the gateway requires a bearer token on every request.
When true (production):
- Every request must include an
Authorization: Bearer <token>header - The gateway calls your external auth service to validate the token and retrieve the user’s identity (email)
- Requests without a valid token are rejected with
401 Unauthorized
When false (development):
- No token is required on any request
- The gateway uses
default_user_identityto determine who the user is - A security warning is logged at startup
default_user_identity
This is the fallback user ID used when authentication is disabled. It tells the gateway “treat all requests as if they came from this user.”
- If
enforce_authentication: falseanddefault_user_identity: "sam_dev_user", every request is treated as coming fromsam_dev_user - If
enforce_authentication: falseanddefault_user_identityis missing or empty, the gateway has no user identity to assign, so it rejects the request with401 Unauthorized
Tip: Setting
default_user_identitytosam_dev_userspecifically is useful because that matches the WebUI’s default user. This means you can see your REST tasks in the WebUI’s Activities tab without any extra configuration. Note that the WebUI is available in the Enterprise edition only.
force_user_identity
This overrides everything, including authenticated users:
- If set, all requests are treated as this user regardless of token or
default_user_identity - Purely a dev/debug tool for testing permission scopes or impersonating a specific user
Authentication: Development Setup
The scaffolded configuration already has authentication disabled with a default user set:
app_config:
enforce_authentication: false
default_user_identity: "sam_dev_user"
No token is needed. Submit requests directly:
# Submit a task (no auth header required)
curl -s -X POST "http://localhost:5050/api/v2/tasks" \
-F "agent_name=OrchestratorAgent" \
-F "prompt=Hello"
# Poll for result
curl -s -X GET "http://localhost:5050/api/v2/tasks/${TASK_ID}"
# List artifacts
curl -s -X GET "http://localhost:5050/api/v2/artifacts/?session_id=${CONTEXT_ID}"
Important: If you see
401 Unauthorizedwithenforce_authentication: false, check thatdefault_user_identityis set in your config. A missing or empty value causes the gateway to reject all requests.
Note: When
enforce_authenticationisfalse, the gateway ignoresAuthorizationheaders entirely. Even if you pass a bearer token, it is not validated. The gateway always usesdefault_user_identityregardless. To test actual token validation, you needenforce_authentication: truewith a running external auth service.
Authentication: Production Setup
For production, enable authentication and configure an external auth service:
app_config:
enforce_authentication: true
external_auth_service_url: ${EXTERNAL_AUTH_SERVICE_URL, https://your-auth-service.com}
external_auth_service_provider: "azure" # or your identity provider
How It Works
- Your client sends an
Authorization: Bearer <token>header with each request - The gateway validates the token by calling
POST {external_auth_service_url}/is_token_validwith the bearer token - If valid, it retrieves the user’s identity by calling
GET {external_auth_service_url}/user_info?provider=<provider> - The user’s email is extracted and forwarded to agents as part of the task context
Where Does the Token Come From?
The REST Entrypoint does not issue tokens. The token comes from your identity provider (Azure AD, Okta, Auth0, etc.) through your standard OAuth/OIDC flow.
If you are running the Agent Mesh WebUI alongside the REST Entrypoint with the same auth service, you can grab a valid token from the WebUI’s browser console:
// Open browser dev tools on the WebUI and run:
console.log(window.localStorage.access_token)
You can also obtain a token programmatically using your identity provider’s token endpoint. For example, with Azure AD:
# Azure AD client credentials flow (example)
curl -s -X POST "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token" \
-F "client_id={your-client-id}" \
-F "client_secret={your-client-secret}" \
-F "scope={your-scope}" \
-F "grant_type=client_credentials"
The response includes an access_token you can use as your bearer token.
Refreshing Tokens
If your token expires, the REST Entrypoint supports token refresh via the auth service:
curl -s -X POST "${EXTERNAL_AUTH_SERVICE_URL}/refresh_token" \
-H "Content-Type: application/json" \
-d '{"provider": "azure", "refresh_token": "${REFRESH_TOKEN}"}'
The response includes new access_token and refresh_token values.
Prerequisites for Testing with Authentication
To test authentication, you need:
enforce_authentication: truein your gateway config- A running external auth service at the configured
external_auth_service_url - A valid bearer token from your identity provider
Without a running auth service, the gateway will return 503 Authentication service is unavailable on every request. If you are doing local development without an auth service, use the development setup (enforce_authentication: false) instead.
Testing with Authentication Enabled
# 1. Get your token (from WebUI console or your identity provider)
export SAM_TOKEN="<your-bearer-token>"
# 2. Submit a task
RESPONSE=$(curl -s -X POST "http://localhost:5050/api/v2/tasks" \
-H "Authorization: Bearer ${SAM_TOKEN}" \
-F "agent_name=OrchestratorAgent" \
-F "prompt=What agents are available?")
TASK_ID=$(echo $RESPONSE | jq -r '.taskId')
echo "Task ID: ${TASK_ID}"
# 3. Poll for result
curl -s -X GET "http://localhost:5050/api/v2/tasks/${TASK_ID}" \
-H "Authorization: Bearer ${SAM_TOKEN}"
# 4. List artifacts
CONTEXT_ID="<contextId from poll response>"
curl -s -X GET "http://localhost:5050/api/v2/artifacts/?session_id=${CONTEXT_ID}" \
-H "Authorization: Bearer ${SAM_TOKEN}"
Error Responses (only when enforce_authentication: true)
These errors only occur when authentication is enforced. With enforce_authentication: false, the gateway accepts all requests regardless of the token.
| Scenario | HTTP Status | Message |
|---|---|---|
No Authorization header |
401 |
"Bearer token not provided." |
| Invalid or expired token | 401 |
"Invalid or expired token." |
| Auth service unreachable | 503 |
"Authentication service is unavailable." |
| User email not in auth response | 401 |
"User email not provided by auth provider" |
| User not found in identity service | 403 |
"User not authorized for this application" |
Session Management
Each REST API request creates a fresh session with a unique ID (rest-session-{uuid}). There is no persistent conversation state between requests.
This means:
- Every request starts a new conversation with the agent
- Artifacts are scoped to the session they were created in
- The
contextIdin the poll response is the session ID you need for artifact retrieval
If you need multi-turn conversations, consider using the WebUI Entrypoint (which supports persistent sessions) or building a client that manages session continuity at the application level.
REST vs. Webhook: Which One to Use?
Both are HTTP-based Entrypoints, but they serve different purposes:
| REST | Webhook | |
|---|---|---|
| Direction | Bidirectional: submit tasks and retrieve results | Unidirectional: ingest events, no result return |
| Endpoints | Fixed API paths (/api/v2/tasks, /api/v2/artifacts) |
Dynamic, user-defined paths (/hooks/data-feed) |
| Payload | Multipart form data (agent_name, prompt, files) | Flexible: JSON, YAML, text, binary per endpoint |
| Auth | Centralized bearer token for all endpoints | Per-endpoint: token, basic, or none |
| Use case | Apps, scripts, CI/CD needing agent responses | Incoming webhooks from GitHub, Stripe, IoT |
Use the REST Entrypoint when your application needs to send a request and get a response. Use the Webhook Entrypoint when external systems push events that should trigger agent processing without waiting for a result.
Last Words of Wisdom
-
Handle consume-once results. The poll endpoint returns the result exactly once, then deletes it. Your client should store the response on first retrieval.
-
Poll with backoff. Start with a short interval (1-2 seconds) and increase if the task takes longer. Results expire after 10 minutes if not retrieved, so make sure your client polls within that window.
-
Retrieve artifacts before the session expires. Artifacts are scoped to the session. If you need the files, download them as part of your workflow.
-
Use Swagger for exploration. Visit
/api/v2/docsto explore the API interactively. No authentication required for the docs endpoint. -
Scope your
gateway_id. Every Entrypoint instance needs a unique ID. Use environment variables (gateway_id: ${MY_REST_GW_ID, rest-gw-01}) so you can run multiple instances safely. -
Watch your ports. The REST Entrypoint defaults to port 8080, which conflicts with the local Solace broker. Use a free port like 5050 in your config.
-
Use consistent identity across gateways. Set
default_user_identity: "sam_dev_user"in your REST gateway config to match the WebUI’s default. This ensures consistent user identity when running multiple gateways against the same broker.
For more information about Solace Agent Mesh, visit the repository:
An event-driven framework designed to build and orchestrate multi-agent AI systems. It enables seamless integration of AI agents with real-world data sources and systems, facilitating complex, multi-step workflows.