Jenova Agent API Reference
Base URL: https://api.jenova.ai/v1
Authentication: Bearer token in Authorization header
Table of Contents
- Overview
- Quick Start
- Core Concepts
- Authentication
- API Endpoints
- Streaming (SSE)
- MCP Server Integration
- Billing
- Rate Limits
- Error Handling
- Pagination
- Localization
- Privacy and Data
- Support
Overview
Build and run production-ready AI agents without assembling the underlying stack yourself. Jenova Agent API brings every core capability together in a single managed service:
The Full Agent Stack
- Agent Orchestration: A unified orchestration layer coordinates models, tools, memory, and retrieval across complex workflows.
- Memory & Context: Unlimited conversation memory and context built into every session. No external state management required.
- Tools & MCP: Unlimited tool integrations with platform-native tools and any remote MCP server, ready out of the box.
- Use Any Model: Power your agents with models from OpenAI, Anthropic, Google, xAI, Qwen, and more through a single integration.
- Fully-Managed Storage: Managed relational and vector databases with built-in RAG. No infrastructure to provision or scale.
- Production-Grade: Used by hundreds of thousands of users. Fully-managed infrastructure, stable APIs, built for production traffic.
Quick Start
1. Get Your API Key
Generate an API key from the developer dashboard at www.jenova.ai/platform. Keys use the format jnv_sk_* and are passed as Bearer tokens.
2. Pick or Create an Agent
Choose a pre-built agent from the platform, or create a custom agent in the dashboard with instructions, model settings, knowledge base files, tools, and MCP servers.
3. Send Your First Message
Create a session and send a message in one call:
curl -N -X POST https://api.jenova.ai/v1/messages \
-H "Authorization: Bearer jnv_sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"agent": "my-support-agent",
"user": "user_12345",
"content": "What can you help me with?"
}'
The response streams back as Server-Sent Events:
event: stream_started
data: {"session_id":"ses_xyz789","run_id":"run_abc123","agent":"my-support-agent"}
event: stream_delta
data: {"session_id":"ses_xyz789","run_id":"run_abc123","chunk_content":"I can help you with ","seq":1}
event: stream_ended
data: {"session_id":"ses_xyz789","run_id":"run_abc123","success":true,"stop_reason":"end_run","usage":{"cost":"0.0032"}}
Capture session_id from stream_started for follow-up requests. For synchronous JSON responses, see Send Message.
Core Concepts
Agent
An AI agent you interact with through the API. Each agent has a unique slug (e.g., my-support-agent) used as the agent value in API calls.
- Pre-built: Select from existing agents on the platform.
- Custom: Configure your own in the dashboard with instructions, model, knowledge base, tools, and MCP servers.
Session
An independent conversation thread between an end user and an agent.
- Identifier: Prefixed ID (e.g.,
ses_abc123) - Scope: Multiple sessions can exist for the same agent and end user, each with independent conversation state
- Lifecycle: Sessions persist indefinitely until deleted via API. For no-storage one-shot tasks, use
POST /messageswithephemeral: true - Platform isolation: API sessions are separate from conversations in the Jenova web app. End users, session history, and billing are independent between the API and web app.
Message
A single entry in a session's conversation history, returned by the Messages endpoints. Each message includes a structured from object with type ("user" or "agent") and name, plus a message type:
external- a conversation message intended to be displayed as chat content.internal- an optional message representing agent work steps during a run, such as tool calls or retrieval.
Run
A single agent execution created when you send a message. A run has a run_id, may stream events while it is active, and produces one or more completed messages. Each session can have only one active run at a time.
End User
The user field scopes sessions to an end user in your application. Use a stable opaque ID, such as your internal user ID or UUID. Avoid emails or other PII unless your application requires them. Sessions created with the same user value are grouped together, enabling per-user session listing.
If user is omitted, the session is scoped to your developer account and cannot be filtered by end user later. Pass user in production.
For existing-session requests, user is an optional ownership guard. If you provide it, it must match the user value used when the session was created; otherwise the API returns 404 session_not_owned. Send it as a query parameter on GET and DELETE requests, and in the JSON body on POST and PATCH requests.
Authentication
Authenticate every request with a Bearer token in the Authorization header:
Authorization: Bearer jnv_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
API keys are generated from the developer dashboard.
User-Agent is optional. SDKs may set it for diagnostics, but the API does not require it.
Key format: Keys start with the prefix jnv_sk_ followed by a base62-encoded random string.
Limits: Each developer account can have up to 10 active API keys.
API Endpoints
Messages
Message sends are the primary API path. Use POST /messages for the first message; it creates the session and starts the run in one request. Use POST /sessions/{session_id}/messages when continuing a captured session_id.
Send Message
POST /messages
Creates a persistent session and sends the first message in one atomic request. Set ephemeral: true for a no-storage streaming one-shot request that stores no session or message history, returns no session ID, and cannot be continued.
Request body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
agent | string | Yes | - | The agent's slug identifier |
content | string | Conditional | - | Message text. Required unless file_urls is provided |
file_urls | string[] | Conditional | - | URLs of files to attach. Required unless content is provided |
user | string | No | - | Your external end user identifier (max 255 characters). If omitted, defaults to your developer account |
session_name | string | No | - | Display name for the new session (max 200 characters) |
ephemeral | boolean | No | false | No-storage, streaming-only one-shot request. Stores no session or message history, returns no session ID, and cannot be continued |
stream | boolean | No | true | true for SSE streaming, false for JSON. MCP authorization requires streaming |
model | string | No | - | One-time model override for this request only. Use a stable model ID such as claude-sonnet-5. Does not change the session's default model |
Example - Streaming (default)
curl -N -X POST https://api.jenova.ai/v1/messages \
-H "Authorization: Bearer jnv_sk_xxx" \
-H "Content-Type: application/json" \
-d '{
"agent": "my-support-agent",
"user": "user_12345",
"content": "Hello, I need help with my account"
}'
The response is an SSE stream (see Streaming for event format). Persistent requests include the new session_id; ephemeral: true requests omit session_id and must use streaming.
Example - JSON (non-streaming)
curl -X POST https://api.jenova.ai/v1/messages \
-H "Authorization: Bearer jnv_sk_xxx" \
-H "Content-Type: application/json" \
-d '{
"agent": "my-support-agent",
"user": "user_12345",
"content": "Hello, I need help with my account",
"stream": false
}'
Response
Streaming responses emit the SSE events documented in Streaming. Non-streaming responses return the JSON message shape shown in Continue Session, including stop_reason and usage for the completed request.
If a non-streaming run is still processing after 90 seconds, the API returns 202 Accepted with status: "processing", session_id, run_id, and message. The run continues after the response or client disconnect; check the session's messages, or use streaming for longer workflows.
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | missing_required_field | agent is required |
| 400 | invalid_payload | Malformed JSON or a field has an invalid type |
| 400 | bad_request | Invalid ephemeral mode, or user/session_name exceeds its maximum length |
| 400 | content_or_uploaded_files_required | Neither content nor file_urls provided |
| 400 | content_too_long | Message content exceeds maximum token length |
| 400 | exceed_max_upload_files | More than 10 file URLs in a single request |
| 400 | unsupported_file_format | A file URL has an unsupported file extension |
| 400 | invalid_file_url | A file URL is malformed or not HTTPS |
| 400 | invalid_model_selection | Model override is not a valid production model |
| 402 | insufficient_credits | Not enough credits to create the session or send the message |
| 404 | agent_not_found | Agent does not exist or is not accessible to your account |
Continue Session
POST /sessions/{session_id}/messages
Sends a message to an existing persistent session and receives the agent's response. Responses stream via SSE by default; set stream: false for JSON.
Request body
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
content | string | Conditional | - | Message text. Required unless file_urls is provided |
file_urls | string[] | Conditional | - | URLs of files to attach. Required unless content is provided |
stream | boolean | No | true | true for SSE streaming, false for JSON. MCP authorization requires streaming |
model | string | No | - | One-time model override for this request only. Use a stable model ID such as claude-sonnet-5. Does not change the session's default model |
Example - Streaming (default)
curl -N -X POST https://api.jenova.ai/v1/sessions/ses_abc123/messages \
-H "Authorization: Bearer jnv_sk_xxx" \
-H "Content-Type: application/json" \
-d '{
"content": "How do I reset my password?"
}'
Example - JSON (non-streaming)
curl -X POST https://api.jenova.ai/v1/sessions/ses_abc123/messages \
-H "Authorization: Bearer jnv_sk_xxx" \
-H "Content-Type: application/json" \
-d '{
"content": "How do I reset my password?",
"stream": false
}'
Response 200 OK
{
"id": "msg_xyz789",
"session_id": "ses_abc123",
"sequence": 4,
"from": {
"type": "agent",
"name": "my-support-agent"
},
"type": "external",
"time": "2026-05-19T10:31:05Z",
"content": "To reset your password, go to Settings > Security > Change Password...",
"model": "claude-sonnet-5",
"files": [],
"stop_reason": "end_run",
"usage": {
"cost": "0.0015"
}
}
If the session already has an active run or queued messages, the API returns JSON 202 Accepted with status: "queued", session_id, run_id, and message_id. The user message is processed by the active run.
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | invalid_payload | Malformed JSON or a field has an invalid type |
| 400 | content_or_uploaded_files_required | Neither content nor file_urls provided |
| 400 | content_too_long | Message content exceeds maximum token length |
| 400 | exceed_max_upload_files | More than 10 file URLs in a single request |
| 400 | unsupported_file_format | A file URL has an unsupported file extension |
| 400 | invalid_file_url | A file URL is malformed or not HTTPS |
| 400 | invalid_model_selection | Model override is not a valid production model |
| 402 | insufficient_credits | Not enough credits to send a message |
| 404 | session_not_found | Session does not exist |
| 404 | session_not_owned | Session belongs to another developer or does not match the supplied user |
Idempotency
POST /messages and POST /sessions/{session_id}/messages accept an optional Idempotency-Key header. Use a unique key for each logical user send so network retries or double-submits do not create duplicate runs.
curl -N -X POST https://api.jenova.ai/v1/messages \
-H "Authorization: Bearer jnv_sk_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: send-user_12345-2026-05-19T10:30:00Z" \
-d '{
"agent": "my-support-agent",
"user": "user_12345",
"content": "Hello"
}'
Non-streaming: Retrying returns the saved 200 or 202 response with the Idempotent-Replayed: true header.
Streaming: The stream is not replayed. Retrying while running or after completion returns an idempotency error with the original run_id and, for persistent requests, session_id. Use GET /sessions/{session_id}/runs/{run_id} to check an active run, or GET /sessions/{session_id}/messages to fetch persisted results.
Completed streaming retry example:
HTTP/1.1 409 Conflict
Content-Type: application/json
{
"error": {
"code": "idempotency_key_reused",
"message": "This Idempotency-Key has already completed. Streaming responses cannot be replayed; use the returned session_id and run_id to retrieve the result."
},
"idempotency": {
"status": "completed",
"session_id": "ses_abc123",
"run_id": "run_abc123"
}
}
Idempotency errors
| Status | Code | Condition |
|---|---|---|
| 409 | idempotency_key_reused | The same key was used with a different request |
| 409 | idempotency_key_in_use | The original request is still running |
| 409 | idempotency_key_reused | The original streaming request already completed and cannot be replayed |
List Messages
GET /sessions/{session_id}/messages
Returns a paginated list of visible conversation messages. The first page contains the most recent messages; within each page, messages are in chronological order (oldest first). sequence is a stable ordering number within the session.
Query parameters
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
limit | integer | 20 | 100 | Messages per page |
cursor | string | - | - | Pagination cursor |
Example
curl "https://api.jenova.ai/v1/sessions/ses_abc123/messages?limit=50" \
-H "Authorization: Bearer jnv_sk_xxx"
Response 200 OK
{
"items": [
{
"id": "msg_002",
"session_id": "ses_abc123",
"sequence": 4,
"from": {
"type": "agent",
"name": "my-support-agent"
},
"type": "external",
"time": "2026-05-19T10:31:05Z",
"content": "To reset your password, go to Settings > Security...",
"model": "claude-sonnet-5",
"files": []
}
],
"next_cursor": "",
"has_more": false
}
Message object
| Field | Type | Description |
|---|---|---|
id | string | Message ID (prefixed with msg_) |
session_id | string | Parent session ID |
sequence | integer | Stable ordering number within the session |
from | object | Sender object with type ("user" or "agent") and name |
type | string | Message type, usually external for visible conversation messages |
time | string | ISO 8601 timestamp |
content | string | Text content. Present on external messages |
model | string | Stable model ID that generated the response. Only present on agent messages |
files | array | Attached or generated files included with the message. Each entry includes file_id, name, url, format, and size when known |
stop_reason | string | Present on completed agent messages. Current value is end_run |
agent | string | Executing agent slug, when available |
agent_name | string | Executing agent display name, when available |
File object
| Field | Type | Description |
|---|---|---|
file_id | string | Jenova file ID, when available |
name | string | File name |
url | string | File URL, when available |
format | string | Lowercase file format, such as pdf, png, or csv |
size | integer | File size in bytes, when known |
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | bad_request | Invalid query parameter |
| 404 | session_not_found | Session does not exist |
| 404 | session_not_owned | Session belongs to another developer or does not match the supplied user |
Get Message
GET /sessions/{session_id}/messages/{message_id}
Retrieves a single visible message by ID.
Response 200 OK
Returns a single message object with the same structure as the list response.
Errors
| Status | Code | Condition |
|---|---|---|
| 404 | session_not_found | Session does not exist |
| 404 | session_not_owned | Session belongs to another developer or does not match the supplied user |
| 404 | not_found | Message does not exist in this session |
File Attachments
Pass publicly accessible HTTPS URLs in the file_urls field.
| Limit | Value |
|---|---|
| Max files per message | 10 |
| Max file size | 20 MB per file |
Supported formats
- Images: JPG, JPEG, PNG, WebP
- Documents: PDF, DOCX, XLSX, PPTX, TXT, CSV, RTF, MD, HTML, XML, JSON, LOG
- Code: JS, TS, TSX, JSX, PY, Java, Go, C, CPP, H, HPP, CS, RB, PHP, RS, Swift, KT, Scala, SQL, CSS, YAML, YML
When listing messages, attached files appear in the message's files array.
Sessions
Sessions are persistent conversations between an end user and an agent. Most integrations can create them implicitly with POST /messages.
Create Session
POST /sessions
Creates an empty persistent session bound to a specific agent. Use this when you need a session ID before the first message; otherwise prefer POST /messages.
Note:
ephemeralis not accepted onPOST /sessions; usePOST /messageswithephemeral: truefor no-storage one-off requests.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
agent | string | Yes | The agent's slug identifier |
user | string | No | Your external end user identifier (max 255 characters). If omitted, defaults to your developer account |
session_name | string | No | Display name for the session (max 200 characters) |
Example
curl -X POST https://api.jenova.ai/v1/sessions \
-H "Authorization: Bearer jnv_sk_xxx" \
-H "Content-Type: application/json" \
-d '{
"agent": "my-support-agent",
"user": "user_12345",
"session_name": "Billing inquiry"
}'
Response 201 Created
{
"id": "ses_abc123",
"session_name": "Billing inquiry",
"agent": "my-support-agent",
"user": "user_12345",
"model": "claude-sonnet-5",
"created_at": "2026-05-19T10:30:00Z",
"updated_at": "2026-05-19T10:30:00Z"
}
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | invalid_payload | Malformed JSON or a field has an invalid type |
| 400 | missing_required_field | agent is required |
| 400 | bad_request | ephemeral was provided, or user/session_name exceeds its maximum length |
| 402 | insufficient_credits | Not enough credits to create a session |
| 404 | agent_not_found | Agent does not exist or is not accessible to your account |
List Sessions
GET /sessions
Returns a paginated list of your sessions, ordered by most recently updated.
Query parameters
| Parameter | Type | Description |
|---|---|---|
limit | integer | Items per page (default 20, max 100) |
cursor | string | Pagination cursor |
user | string | Filter by end user identifier (max 255 characters) |
agent | string | Filter by agent slug |
Example
curl "https://api.jenova.ai/v1/sessions?user=user_12345&limit=10" \
-H "Authorization: Bearer jnv_sk_xxx"
Response 200 OK
{
"items": [
{
"id": "ses_abc123",
"session_name": "Billing inquiry",
"agent": "my-support-agent",
"user": "user_12345",
"model": "claude-sonnet-5",
"created_at": "2026-05-19T10:30:00Z",
"updated_at": "2026-05-19T11:15:00Z"
}
],
"next_cursor": "eyJ2IjoxLCJrIjoiY3VyXzAxIn0",
"has_more": true
}
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | bad_request | Invalid query parameter |
Get Session
GET /sessions/{session_id}
Retrieves a single session by ID.
Response 200 OK
Returns a session object with the same structure as the create response.
Errors
| Status | Code | Condition |
|---|---|---|
| 404 | session_not_found | Session does not exist |
| 404 | session_not_owned | Session belongs to another developer or does not match the supplied user |
Rename Session
PATCH /sessions/{session_id}
Updates the display name of a session.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
session_name | string | Yes | New display name (max 200 characters) |
Response 200 OK
Returns the updated session object.
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | invalid_payload | Malformed JSON or a field has an invalid type |
| 400 | missing_required_field | session_name is required |
| 400 | bad_request | session_name exceeds its maximum length |
| 404 | session_not_found | Session does not exist |
| 404 | session_not_owned | Session belongs to another developer or does not match the supplied user |
Delete Session
DELETE /sessions/{session_id}
Permanently deletes a session and all of its messages. The session must not have an active run.
Response 204 No Content
Errors
| Status | Code | Condition |
|---|---|---|
| 404 | session_not_found | Session does not exist |
| 404 | session_not_owned | Session belongs to another developer or does not match the supplied user |
| 409 | busy | Session has an active run - cancel it first |
Operations
These endpoints are recovery and editing controls for persistent sessions. Most integrations only need Cancel; use the other operations when you intentionally want to alter or recover session state. All operations support the optional user ownership guard described in End User.
Cancel Active Run
POST /sessions/{session_id}/cancel
Cancels the current in-progress agent run. This does not delete messages that were already completed before cancellation.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
run_id | string | No | Optional stale-run guard. If supplied and it does not match the active run, the API returns 409 stale_run |
Response 204 No Content
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | cancel_not_allowed | No active run to cancel, or cancellation not permitted |
| 404 | session_not_found | Session does not exist |
| 404 | session_not_owned | Session belongs to another developer or does not match the supplied user |
| 409 | stale_run | Provided run_id does not match the active run |
Undo Active Run
POST /sessions/{session_id}/undo
Cancels the active run, waits for it to stop, then removes any messages it already added. No additional output is persisted after the undo is issued.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
run_id | string | No | Optional stale-run guard. If supplied and it does not match the active run, the API returns 409 stale_run |
Response 200 OK
{
"session_id": "ses_abc123",
"run_id": "run_abc123",
"deleted": ["msg_002", "msg_001"]
}
If the run is cancelled before any message is completed, deleted is an empty array.
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | cancel_not_allowed | No active run to undo, or cancellation not permitted |
| 404 | session_not_found | Session does not exist |
| 404 | session_not_owned | Session belongs to another developer or does not match the supplied user |
| 409 | stale_run | Provided run_id does not match the active run |
| 409 | busy | The session is temporarily unavailable because another update is in progress |
Delete Recent Messages
POST /sessions/{session_id}/messages/delete
Removes the N most recent messages from an idle session. The session must not have an active run.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
count | integer | Yes | Number of recent messages to delete from the end (must be greater than 0) |
Response 200 OK
{
"deleted": ["msg_002", "msg_001"]
}
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | bad_request | count missing, zero, or negative; or no messages to delete |
| 404 | session_not_found | Session does not exist |
| 404 | session_not_owned | Session belongs to another developer or does not match the supplied user |
| 409 | busy | Session has an active run |
Fork Session
POST /sessions/{session_id}/fork
Creates a new session by copying the source session up to a specific message. The source session must not have an active run.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
message_id | string | No | Fork point message ID. If omitted, forks from the last message |
Response 201 Created
Returns the newly created session object with the same structure as session creation.
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | bad_request | message_id is invalid |
| 402 | insufficient_credits | Not enough credits to fork a session |
| 404 | not_found | message_id does not exist in this session |
| 404 | session_not_found | Session does not exist |
| 404 | session_not_owned | Session belongs to another developer or does not match the supplied user |
| 409 | busy | Source session has an active run |
Get Run Status
GET /sessions/{session_id}/runs/{run_id}
Returns the current state of an active run. Use this after a dropped SSE connection or an idempotency response that returned a run_id. After a run finishes, fetch results with GET /sessions/{session_id}/messages.
Response 200 OK
{
"session_id": "ses_abc123",
"run_id": "run_abc123",
"status": "streaming",
"started_at": "2026-05-19T10:30:00Z",
"updated_at": "2026-05-19T10:30:06Z",
"content": "Partial visible response text so far"
}
The response may also include recent progress hints while the run is active.
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | bad_request | The session is ephemeral |
| 404 | session_not_found | Session does not exist |
| 404 | session_not_owned | Session belongs to another developer or does not match the supplied user |
| 404 | not_found | The run is not active for this session |
Credits
Get Balance
GET /credits/balance
Returns your current credit balance.
Response 200 OK
{
"balance": "123.45"
}
Agents
Create and edit custom agents in the dashboard. API support for creating and editing agents is coming soon.
Scheduled/background workflows are not currently supported through the API. Support is coming soon.
List Agents
GET /agents
Returns the agents available to your API key. Use the agent value when creating sessions or sending messages.
Response 200 OK
{
"agents": [
{
"agent": "jenova",
"display_name": "Jenova",
"description": "General-purpose Jenova agent"
},
{
"agent": "my-support-agent",
"display_name": "Support Agent",
"description": "Answers customer questions"
}
]
}
| Field | Type | Description |
|---|---|---|
agent | string | Stable agent slug to pass as the agent value |
display_name | string | Human-readable display name |
description | string | Agent description |
Models
List Models
GET /models
Returns all models available for use in the model field when sending messages.
Response 200 OK
{
"models": [
{
"id": "claude-opus-4-8",
"name": "Claude Opus 4.8",
"thinking_variant": "claude-opus-4-8-thinking"
},
{
"id": "claude-opus-4-8-thinking",
"name": "Claude Opus 4.8 (Thinking)"
},
{
"id": "kimi-k2.6",
"name": "Kimi K2.6",
"thinking_variant": "kimi-k2.6-thinking"
}
]
}
| Field | Type | Description |
|---|---|---|
id | string | Stable model identifier. Pass this as the model value in Send Message |
name | string | Human-readable display name |
thinking_variant | string | Model ID of the thinking/reasoning variant. Present only on base models that support reasoning |
Models with a thinking_variant support extended reasoning. Use the variant ID directly in the model field to enable it.
If no model is specified when sending a message, the agent's default model is used.
Docs
GET /docs?lang=en
GET /doc?lang=en
Returns this reference as Markdown. Use lang to select the language.
Streaming (SSE)
When stream is omitted or true (the default), message responses are delivered as Server-Sent Events. Use message_completed events to identify messages that are ready to fetch or render.
Timeout: SSE connections stay open for up to 60 minutes. Non-streaming requests wait up to 90 seconds, then return 202 Accepted while the run continues.
Connection headers
The SSE response sets these headers:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
X-Accel-Buffering: no
X-Run-Id: run_abc123
X-Run-Id is available before the first SSE event.
Reconnect and recovery
SSE streams are not replayed. If the connection drops, use the captured session_id and run_id to recover state:
curl "https://api.jenova.ai/v1/sessions/ses_abc123/runs/run_abc123" \
-H "Authorization: Bearer jnv_sk_xxx"
If the run is still active, this returns the current status, partial text, and recent progress. If it returns 404 not_found, the run is no longer active; fetch the session's messages to reconcile completed output:
curl "https://api.jenova.ai/v1/sessions/ses_abc123/messages?limit=20" \
-H "Authorization: Bearer jnv_sk_xxx"
Frame format
Each SSE frame follows the standard format:
event: <event_type>
data: <json_payload>
Two newlines terminate each frame.
Event types
Streams include lifecycle, text delta, thinking, progress, warning, message completion, MCP connection, error, final, and ping events. Some event types are only sent when relevant.
For ephemeral requests (ephemeral: true), every SSE event omits session_id. Use run_id only to correlate events inside that one stream.
For final reconciliation on persistent requests, wait for stream_ended, then call List Messages.
Common fields on run-scoped events:
| Field | Description |
|---|---|
session_id | Session ID. Omitted for ephemeral streams. Capture this from stream_started for follow-up requests when using persistent POST /messages |
run_id | Current run ID, when available |
stream_started
Sent once when the run starts.
| Field | Description |
|---|---|
agent | Session agent slug, when available |
stream_delta
Sent repeatedly as the agent generates visible response text. Concatenate chunk_content values in seq order to build the streamed response.
event: stream_delta
data: {"session_id":"ses_abc123","run_id":"run_abc123","chunk_content":"To reset your ","seq":1}
| Field | Description |
|---|---|
chunk_content | Text chunk |
seq | Monotonic chunk sequence within the stream |
stream_thinking
Sent repeatedly while the agent emits thinking output. Use this for a separate thinking indicator or trace view; do not concatenate it into the final response text.
| Field | Description |
|---|---|
content | Thinking text chunk |
stream_progress
Reports user-visible activity during generation, such as reading a document, searching the web, or waiting for a user action. These events are intended for transient UI display; ignore unknown fields. Use completed messages for the authoritative message history.
Advanced: message requests accept include_progress: false to omit only stream_progress. Lifecycle events, ping, message_completed, errors, and terminal events are still sent when relevant.
| Field | Description |
|---|---|
state | Lifecycle state: running, in-progress, success, failed, skipped, complete, cancelled, among others. Handle unknown values gracefully |
label | Human-readable activity label |
Some progress events may include url, file_name, or server_name as optional display hints.
message_completed
Sent each time a message is complete and ready to fetch or render.
This is a boundary marker, not the full message object. Fetch the message if you need content or metadata.
event: message_completed
data: {"session_id":"ses_abc123","run_id":"run_abc123","message_id":"msg_abc123","sequence":4,"from":{"type":"agent","name":"Jenova"},"type":"external"}
| Field | Description |
|---|---|
message_id | Completed message ID |
sequence | Stable ordering number within the session |
from | Sender object with type (user or agent) and name |
type | Message type: external or internal |
mcp_connection
Sent when the agent needs the end user to connect or authorize one or more MCP servers before it can continue. This event is only available in streaming mode.
| Field | Description |
|---|---|
connection_server_list | MCP servers that need a connection action. Each server includes mcp_server_id, mcp_server_name, and optional auth_url |
user_action_deadline_unix | Unix timestamp when the connection user action expires |
mcp_connection_resolved
Sent when the MCP connection user action has been resolved or has expired.
No additional fields beyond the common run-scoped fields.
warning
Sent for non-fatal warnings during a run.
| Field | Description |
|---|---|
message | Human-readable non-fatal warning |
code | Optional warning code |
stream_error
Sent when a run fails. A final stream_ended event may follow with success:false and stop_reason:"error".
| Field | Description |
|---|---|
code | Error code |
message | Human-readable error message |
stream_ended
Sent once when the run finishes. This is the final event for the stream.
event: stream_ended
data: {"session_id":"ses_abc123","run_id":"run_abc123","success":true,"stop_reason":"end_run","usage":{"cost":"0.0032"}}
Failure example:
event: stream_ended
data: {"session_id":"ses_abc123","run_id":"run_abc123","success":false,"stop_reason":"user_cancelled","usage":{"cost":"0.0012"}}
| Field | Description |
|---|---|
success | Whether the run completed successfully |
stop_reason | Terminal run reason: end_run, user_cancelled, user_action_timeout, or error |
usage | Usage object for this request. Currently includes cost when available |
ping
Keepalive frames sent every 15 seconds to prevent proxy/CDN timeouts. Ignore these in your client.
MCP Server Integration
Connect your agents to external tools via the Model Context Protocol (MCP):
- Jenova-managed MCP servers: Jenova-hosted servers for search, content retrieval, document generation, and other built-in capabilities
- Remote MCP servers: Other remote MCP servers configured for your agent
MCP servers must be configured in the dashboard when creating or editing your agent. To use your own MCP server, add it to a custom agent, then call that agent through the API. The API executes tools enabled in the agent's configuration; no additional setup is required in API requests.
When an agent uses MCP tools during a response, progress events are sent in the stream as they occur:
event: stream_progress
data: {"session_id":"ses_...","run_id":"run_...","state":"running","label":"Searching Google"}
If the agent needs the end user to connect or authorize an MCP server during the run, streaming responses may include mcp_connection and mcp_connection_resolved events. Present the connection server list to your end user and open the provided auth_url when present. Keep the SSE stream open while the user connects or authorizes the server.
After authorization, Jenova stores the token, the authorization window shows a completion page, and the same run continues automatically. The end user does not need to resend the message.
If the end user does not connect, authorize, skip, or mute before user_action_deadline_unix, the run ends with stop_reason:"user_action_timeout". You may also cancel the active run with POST /sessions/{session_id}/cancel.
Store the auth_url client-side. If the client disconnects during authorization, the URL remains valid until user_action_deadline_unix. For persistent requests, reconnect with Get Run Status, or fetch messages after the run finishes.
Non-streaming requests (stream: false) do not support MCP connection user actions; use streaming for agents that may need this interaction.
Skip MCP Connection
POST /sessions/{session_id}/mcp/connection/skip
Dismisses a pending MCP connection user action and lets the run continue without that connection.
To mute future connection prompts for one server for the same API user, include mcp_server_id and mute. Supported values are 24h and forever.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
run_id | string | Yes | Active run ID from the mcp_connection event |
mcp_server_id | string | No | Required with mute; use the mcp_server_id from the event |
mute | string | No | 24h or forever |
Response 204 No Content
The stream emits mcp_connection_resolved, then the run continues with the same run_id.
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | bad_request | Missing run_id, no active run, or no MCP connection to skip |
| 400 | bad_request | Invalid mute, or missing mcp_server_id when mute is set |
| 404 | session_not_found | Session does not exist |
| 404 | session_not_owned | Does not match the supplied user |
| 409 | stale_run | run_id does not match the active run |
Billing
All costs are deducted from your developer credit balance. View usage and top up credits at www.jenova.ai/platform.
Pricing
| Operation | Cost |
|---|---|
| Create Session | $0.01 flat for every persistent session, including sessions created implicitly by POST /messages |
| Fork Session | $0.05 flat |
| Send Message | Variable (see below) |
Message cost depends on:
- Model - different models have different per-token costs
- Context length - longer sessions consume more input tokens per request
- Workflow complexity - longer workflows and heavier tool use (web search, file generation, document analysis) increase total token consumption
The actual cost is returned as stream_ended.usage.cost for streaming requests and usage.cost for non-streaming JSON requests.
Credit holds
Each new message run places a $0.50 hold on your credit balance before execution begins. This reserves funds for the run. Queued follow-up messages in an active run do not create additional holds; the active run's usage is checked against your remaining balance.
When the run completes, the hold is settled to the actual cost and the difference is released. Cancelled and failed runs are charged only for usage already incurred. If a request fails before reaching the model, the full hold is released.
This means your available balance may temporarily appear lower during in-flight requests. You need at least $0.50 in available balance to send a message to an existing session or to send an ephemeral message. A first persistent POST /messages request creates a session and needs at least $0.51 to cover the message hold plus the session creation fee.
Rate Limits
Every developer account is subject to three rate limit dimensions:
| Dimension | Default | Description |
|---|---|---|
| RPM (Requests Per Minute) | 60 | Fixed window per minute |
| RPD (Requests Per Day) | 1,000 | Fixed window per day |
| Concurrent | 5 | Maximum simultaneous in-flight requests |
GET and HEAD requests do not consume concurrent slots. cancel, undo, and mcp/connection/skip do not consume concurrent slots, so these operations remain available when all slots are in use. These requests still count toward RPM and RPD.
Response headers
Authenticated API responses include rate limit headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Your RPM limit |
X-RateLimit-Remaining | Requests remaining in the current minute window |
X-RateLimit-Reset | Unix timestamp when the current window resets |
Retry-After | Seconds to wait before retrying (only on 429) |
When a limit is exceeded, the API returns 429 Too Many Requests:
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Please retry after 12 seconds."
}
}
Error Handling
Immediate HTTP errors and non-streaming run errors follow a consistent envelope:
{
"error": {
"code": "error_code_string",
"message": "Human-readable description"
}
}
Error messages are localized based on the lang parameter (see Localization).
Streaming run errors are delivered as stream_error events. A failed run may still send a final stream_ended event with success:false and stop_reason set. Non-streaming run errors may also include a top-level usage object when cost data is available.
Endpoint-specific errors are documented inline under each endpoint.
Run Errors After Start
Run errors surface after a message run has already started. In streaming mode, they appear as stream_error events and may be followed by stream_ended with success:false. In non-streaming mode, they are returned as a JSON error response with the HTTP status below.
| Non-streaming HTTP Status | Code | Description |
|---|---|---|
| 400 | content_policy_violation | The model provider rejected the request for content policy reasons |
| 404 | session_not_found | Session was deleted before the run could execute |
| 409 | busy | Session became busy or temporarily unavailable before the run could start |
| 413 | total_image_size_exceeded | Combined image payload exceeds the model's per-request size limit |
| 500 | internal_error | Unexpected run failure |
| 502 | llm_api_error | Model provider or upstream model API error |
Pagination
List endpoints use cursor-based pagination:
{
"items": [],
"next_cursor": "eyJ2IjoxLCJrIjoiY3VyXzAyIn0",
"has_more": true
}
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
limit | integer | 20 | 100 | Number of items per page |
cursor | string | - | - | Opaque cursor from a previous next_cursor |
Pass next_cursor as the cursor query parameter to fetch the next page. When has_more is false, there are no more results.
Localization
All endpoints accept an optional lang query parameter to control the language of error messages and any localized content.
| Source | Priority | Example |
|---|---|---|
lang query parameter | Highest | ?lang=zh |
Accept-Language header | Fallback | Accept-Language: ja |
| Default | Lowest | English (en) |
You can append ?lang=xx to any request URL:
POST /sessions?lang=zh
GET /sessions/ses_abc123/messages?lang=ja
Supported languages: en, zh, ja, ko, es, fr, de, it, pt, ru, id, th, vi
Privacy and Data
Jenova does not use API prompts, outputs, conversation history, uploaded files, agent instructions, or knowledge bases to train Jenova models.
For third-party model providers, Jenova uses commercial API channels, account settings, contractual commitments, or opt-outs intended to prevent customer content from being used to train provider models.
Jenova stores and processes API data using U.S. infrastructure. Third-party providers may process data in other jurisdictions as described in the Privacy Policy and Terms of Use.
For full details, see the Terms of Use, Privacy Policy, and Usage Policy.
Support
- Dashboard: www.jenova.ai/platform
- Email: [email protected]