Jenova - The AI Agent PlatformAPI Platform

Jenova Agent API Reference

Base URL: https://api.jenova.ai/v1

Authentication: Bearer token in Authorization header


Table of Contents


Overview

Run custom agents in your product or workflow in minutes. Set up your agents for any task, choose any model and tools, and let Jenova handle the harness, database, and scale.

The Full Agent Stack:

  • Managed Agent Harness: One call in, finished work out. Your agent researches, reasons, calls tools, and produces the files — exactly as you instruct.
  • Unbounded Context & Memory: RAG-powered context management and proprietary session state tracking enable unlimited memory within conversations.
  • Library of Tools & MCP: Connect to our library of 30+ tools and MCP servers, such as search, image generation, email, and more — or plug in your own MCP servers.
  • Seamlessly Switch to Any Model: Deploy models from OpenAI, Anthropic, Google, xAI, or leading open-source providers, and switch between them with one parameter.
  • Managed Knowledge & RAG: Jenova handles the end-to-end RAG pipeline — chunking, indexing, storage, and retrieval.
  • Proven at Scale: Deploy on battle-tested infrastructure running hundreds of live agents in production. Zero clusters to provision, scale, or maintain.

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 one-shot tasks that do not need a session, use POST /messages with ephemeral: 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 may produce 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 requests to an existing session (any endpoint with {session_id} in its path), 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. A user query parameter on POST or PATCH returns 400 bad_request.


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 request. Set ephemeral: true for a streaming one-shot request that returns no session ID and cannot be continued. Its data is deleted shortly after the request completes.

Request body

FieldTypeRequiredDefaultDescription
agentstringYes-The agent's slug identifier
contentstringConditional-Message text. Required unless file_urls is provided
file_urlsstring[]Conditional-URLs of files to attach. Required unless content is provided
userstringNo-Your external end user identifier (max 255 characters). If omitted, defaults to your developer account
session_namestringNo-Display name for the new session (max 200 characters)
ephemeralbooleanNofalseStreaming-only one-shot request. Returns no session ID, cannot be continued, and its data is deleted shortly after the request completes
streambooleanNotruetrue for SSE streaming, false for JSON. MCP authorization requires streaming
include_progressbooleanNotrueSet to false to omit stream_progress events. Streaming only
modelstringNo-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
additional_contextobject[]No-Named context for this request only. See Additional Context

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 message object with stop_reason and usage for the request, as shown in Continue Session. stop_reason is end_run, or user_cancelled if the run was cancelled after the agent produced a message.

If the run is cancelled before the agent produces a message, the API returns 200 OK with status: "cancelled", session_id, run_id, stop_reason: "user_cancelled", and usage.

If a non-streaming run is still processing after 90 seconds, the API returns 202 Accepted with status: "processing", session_id, run_id, and a human-readable message. The run continues after the response or client disconnect; check the session's messages, or use streaming for longer workflows.

Errors

StatusCodeCondition
400missing_required_fieldagent is required
400invalid_payloadMalformed JSON or a field has an invalid type
400bad_requestInvalid ephemeral mode, invalid additional_context, or user/session_name exceeds its maximum length
400content_or_uploaded_files_requiredNeither content nor file_urls provided
400content_too_longMessage content exceeds 32,000 tokens
400exceed_max_upload_filesMore than 10 distinct file URLs in a single request
400unsupported_file_formatA file URL has an unsupported file extension
400invalid_file_urlA file URL is malformed or not HTTPS
400invalid_model_selectionmodel is not a valid model ID, or the agent's default model is not available
402insufficient_creditsNot enough credits to create the session or send the message
404agent_not_foundAgent 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

FieldTypeRequiredDefaultDescription
contentstringConditional-Message text. Required unless file_urls is provided
file_urlsstring[]Conditional-URLs of files to attach. Required unless content is provided
streambooleanNotruetrue for SSE streaming, false for JSON. MCP authorization requires streaming
include_progressbooleanNotrueSet to false to omit stream_progress events. Streaming only
modelstringNo-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
additional_contextobject[]No-Named context for this request only. See Additional Context

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. Requests with additional_context return 409 busy instead.

Non-streaming responses follow the same rules as Send Message, including the status: "cancelled" response when the run is cancelled before the agent produces a message.

Errors

StatusCodeCondition
400invalid_payloadMalformed JSON or a field has an invalid type
400bad_requestInvalid additional_context
400content_or_uploaded_files_requiredNeither content nor file_urls provided
400content_too_longMessage content exceeds 32,000 tokens
400exceed_max_upload_filesMore than 10 distinct file URLs in a single request
400unsupported_file_formatA file URL has an unsupported file extension
400invalid_file_urlA file URL is malformed or not HTTPS
400invalid_model_selectionmodel is not a valid model ID, or the session's model is no longer available
402insufficient_creditsNot enough credits to send a message
404session_not_foundSession does not exist
404session_not_ownedSession belongs to another developer or does not match the supplied user
409busyThe session is temporarily unavailable because another update is in progress, or additional_context was sent while the session has an active run or queued messages
429too_many_pendingThe session already has 200 queued messages
503service_unavailableService temporarily unavailable

Idempotency

POST /messages, POST /sessions/{session_id}/messages, and POST /invoke accept an optional Idempotency-Key header (max 255 characters). Use a unique key for each logical user send so network retries or double-submits do not create duplicate runs. Keys are kept for 24 hours.

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 response with the Idempotent-Replayed: true header, including errors returned after the request started processing. A saved 202 Accepted response is replayed unchanged; use Get Run Status or List Messages to check the result. Requests rejected before processing starts, such as validation or credit errors, are not saved, so a retry is evaluated again.

Streaming: The stream is not replayed. For POST /messages and POST /sessions/{session_id}/messages, retrying while running or after completion returns an idempotency error with the original run_id and, for persistent requests, session_id. For persistent requests, use Get Run Status to check an active run, or List Messages to fetch persisted results. Streamed POST /invoke results cannot be retrieved after the stream closes. POST /invoke retries return the original id instead of run_id and session_id.

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

StatusCodeCondition
400bad_requestIdempotency-Key exceeds its maximum length
409idempotency_key_reusedThe same key was used with a different request
409idempotency_key_in_useThe original request is still running
409idempotency_key_reusedThe original streaming request already completed and cannot be replayed

List Messages

GET /sessions/{session_id}/messages

Returns a paginated list of the session's messages. By default only external conversation messages are returned. The first page contains the most recent messages; within each page, messages are in chronological order (oldest first). sequence is the message's ordering number within the session.

Query parameters

ParameterTypeDefaultMaxDescription
limitinteger20100Messages per page
cursorstring--Pagination cursor
includestringexternal-Message types to return: external, internal, or all

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",
      "stop_reason": "end_run"
    }
  ],
  "next_cursor": "",
  "has_more": false
}

Message object

FieldTypeDescription
idstringMessage ID (prefixed with msg_)
session_idstringParent session ID
sequenceintegerOrdering number within the session. Numbers can be reused after messages are deleted
fromobjectSender object with type (user or agent) and name
typestringexternal for conversation messages, or internal for agent work steps
timestringISO 8601 timestamp
contentstringText content. Present on external messages
modelstringStable model ID that generated the response. Only present on agent messages
filesarrayAttached or generated files included with the message. See File object below. Omitted from List Messages and Get Message when the message has no files
stop_reasonstringPresent on external agent messages with content. Value is end_run
agentstringExecuting agent slug, when available
agent_namestringExecuting agent display name, when available
progress_eventsarrayActivity recorded for an internal message, when available. Each entry has the same fields as a stream_progress event

File object

FieldTypeDescription
file_idstringJenova file ID. Empty when unknown
namestringFile name
urlstringFile URL. Empty when unknown
formatstringLowercase file format, such as pdf, png, or csv
sizeintegerFile size in bytes. 0 when unknown

Errors

StatusCodeCondition
400bad_requestInvalid query parameter
404session_not_foundSession does not exist
404session_not_ownedSession belongs to another developer or does not match the supplied user

Get Message

GET /sessions/{session_id}/messages/{message_id}

Retrieves a single message by ID.

Query parameters

ParameterTypeDefaultDescription
includestringexternalMessage types that can be returned: external, internal, or all

Response 200 OK

Returns a single message object with the same structure as the list response.

Errors

StatusCodeCondition
400bad_requestInvalid query parameter
404session_not_foundSession does not exist
404session_not_ownedSession belongs to another developer or does not match the supplied user
404not_foundMessage does not exist in this session

File Attachments

Pass publicly accessible HTTPS URLs in the file_urls field.

LimitValue
Max files per message10
Max file size20 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

File URLs are fetched when the agent reads them during the run, not when the request is accepted. A URL that cannot be fetched, or a file over 20 MB, is not read; the request itself is not rejected.

When listing messages, attached files appear in the message's files array.


Additional Context

Pass named context for a single message in additional_context, such as details about the end user's account. It is not saved to message history.

FieldTypeRequiredDescription
namestringYesLowercase letters, digits, and underscores, starting with a letter (max 64 characters). Unique within the request, ignoring underscores and plural forms
contentstringYesContext text. Must not be empty (max 12,000 tokens)
LimitValue
Max items per message4
Max tokens per item12,000

Use a descriptive name such as customer_profile or order_details.

An item with a missing or non-string name or content returns 400 invalid_payload. Other invalid items return 400 bad_request.

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?",
    "additional_context": [
      {
        "name": "customer_profile",
        "content": "Plan: Pro. Last login: 2026-05-18."
      }
    ]
  }'

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: ephemeral is not accepted on POST /sessions; use POST /messages with ephemeral: true for one-shot requests.

Request body

FieldTypeRequiredDescription
agentstringYesThe agent's slug identifier
userstringNoYour external end user identifier (max 255 characters). If omitted, defaults to your developer account
session_namestringNoDisplay 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"
}

Session object

FieldTypeDescription
idstringSession ID (prefixed with ses_)
session_namestringDisplay name
agentstringAgent slug
agent_detailsobjectAgent details with display_name. Only present when requested with include=agent
userstringEnd user identifier. null if the session was created without user
modelstringModel used when a message does not specify one. Set to the agent's default model when the session is created. Empty when the agent lets Jenova choose the model
created_atstringISO 8601 timestamp
updated_atstringISO 8601 timestamp

Errors

StatusCodeCondition
400invalid_payloadMalformed JSON or a field has an invalid type
400missing_required_fieldagent is required
400bad_requestephemeral was provided, or user/session_name exceeds its maximum length
402insufficient_creditsNot enough credits to create a session
404agent_not_foundAgent 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

ParameterTypeDefaultMaxDescription
limitinteger20100Items per page
cursorstring--Pagination cursor
userstring--Filter by end user identifier (max 255 characters)
agentstring--Filter by agent slug
includestring--Set to agent to add agent_details to each session

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": "2026-05-19T11:15:00Z|ses_abc123",
  "has_more": true
}

Errors

StatusCodeCondition
400bad_requestInvalid query parameter

Get Session

GET /sessions/{session_id}

Retrieves a single session by ID.

Query parameters

ParameterTypeDescription
includestringSet to agent to add agent_details to the session

Response 200 OK

Returns a session object.

Errors

StatusCodeCondition
400bad_requestInvalid query parameter
404session_not_foundSession does not exist
404session_not_ownedSession 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

FieldTypeRequiredDescription
session_namestringYesNew display name (max 200 characters)

Response 200 OK

Returns the updated session object.

Errors

StatusCodeCondition
400invalid_payloadMalformed JSON or a field has an invalid type
400missing_required_fieldsession_name is required
400bad_requestsession_name exceeds its maximum length
404session_not_foundSession does not exist
404session_not_ownedSession belongs to another developer or does not match the supplied user
409busyThe session is temporarily unavailable because another update is in progress

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

StatusCodeCondition
404session_not_foundSession does not exist
404session_not_ownedSession belongs to another developer or does not match the supplied user
409busySession has an active run - cancel it first

Operations

These endpoints are recovery and editing controls for persistent sessions. Most integrations only need Cancel Active Run; 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 active agent run. This does not delete messages that were already completed before cancellation.

Request body

FieldTypeRequiredDescription
run_idstringNoOptional stale-run guard. If supplied and it does not match the active run, the API returns 409 stale_run

Response 204 No Content

Errors

StatusCodeCondition
400invalid_payloadMalformed JSON or a field has an invalid type
400cancel_not_allowedNo active run to cancel, or cancellation not permitted
404session_not_foundSession does not exist
404session_not_ownedSession belongs to another developer or does not match the supplied user
409stale_runProvided 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

FieldTypeRequiredDescription
run_idstringNoOptional 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

StatusCodeCondition
400invalid_payloadMalformed JSON or a field has an invalid type
400cancel_not_allowedNo active run to undo, or cancellation not permitted
404session_not_foundSession does not exist
404session_not_ownedSession belongs to another developer or does not match the supplied user
409stale_runProvided run_id does not match the active run
409busyThe session is temporarily unavailable because another update is in progress

Delete Recent Messages

POST /sessions/{session_id}/messages/delete

Removes the count most recent messages from an idle session, including internal messages. Use include=all on List Messages to see them. The session must not have an active run.

Request body

FieldTypeRequiredDescription
countintegerYesNumber of recent messages to delete from the end (must be greater than 0). Values above 100 are treated as 100

Response 200 OK

{
  "deleted": ["msg_002", "msg_001"]
}

Errors

StatusCodeCondition
400invalid_payloadMalformed JSON or a field has an invalid type
400bad_requestcount missing, zero, or negative; or no messages to delete
404session_not_foundSession does not exist
404session_not_ownedSession belongs to another developer or does not match the supplied user
409busySession 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 (optional)

FieldTypeRequiredDescription
message_idstringNoFork 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

StatusCodeCondition
400invalid_payloadMalformed JSON or a field has an invalid type
400bad_requestmessage_id is invalid
402insufficient_creditsNot enough credits to fork a session
404not_foundmessage_id does not exist in this session
404session_not_foundSession does not exist
404session_not_ownedSession belongs to another developer or does not match the supplied user
409busySource 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"
}

status is running while the agent is working, streaming once response text is being produced, or ended briefly after the run finishes. The response may also include progress_events with recent activity, in the same format as stream_progress. When status is ended or the request returns 404 not_found, fetch the session's messages.

Errors

StatusCodeCondition
404session_not_foundSession does not exist
404session_not_ownedSession belongs to another developer or does not match the supplied user
404not_foundThe 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 account. 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"
    }
  ]
}
FieldTypeDescription
agentstringStable agent slug to pass as the agent value
display_namestringHuman-readable display name
descriptionstringAgent description

Models

List Models

GET /models

Returns all models available for use in the model field when sending messages or calling POST /invoke.

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": "claude-sonnet-5",
      "name": "Claude Sonnet 5",
      "thinking_variant": "claude-sonnet-5-thinking"
    }
  ]
}
FieldTypeDescription
idstringStable model identifier. Pass this as the model value in Send Message or Invoke Model
namestringHuman-readable display name
thinking_variantstringModel 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 session's model is used. It is set to the agent's default model when the session is created.


Invoke Model

POST /invoke

Sends a prompt directly to a model, without an agent, session, or tools. Responses stream via SSE by default; set stream: false for JSON.

Request body

FieldTypeRequiredDefaultDescription
modelstringYes-Model ID from List Models
contentstringYes-Prompt, including any instructions
streambooleanNotruetrue for SSE streaming, false for JSON
userstringNo-Your external end user identifier (max 255 characters). If omitted, defaults to your developer account

Example

curl -X POST https://api.jenova.ai/v1/invoke \
  -H "Authorization: Bearer jnv_sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-5",
    "content": "Describe a forest in one sentence.",
    "stream": false
  }'

Response 200 OK

{
  "id": "inv_abc123",
  "model": "claude-sonnet-5",
  "content": "Sunlight filters through tall pines onto a quiet, mossy floor.",
  "usage": {
    "cost": "0.0004"
  }
}

Streaming responses send stream_started, then stream_delta events as the model generates text, and end with stream_ended, which carries usage. Concatenate chunk_content values in seq order to build the streamed response. If the request fails, stream_error is sent before stream_ended. Events carry id instead of session_id and run_id.

usage.cost is null when the cost is unavailable. If a non-streaming request fails after the model call starts, the error response includes top-level id and usage fields.

Streaming requests are limited to 120 minutes. Non-streaming requests wait up to 30 minutes for the result instead of returning 202 Accepted, and return an error if the result is not ready by then.

Errors

StatusCodeCondition
400invalid_payloadMalformed JSON or a field has an invalid type
400missing_required_fieldcontent is required
400content_too_longContent exceeds 32,000 tokens
400bad_requestuser exceeds its maximum length
400invalid_model_selectionmodel is missing or not a valid model ID
400content_policy_violationThe model provider rejected the request for content policy reasons
400llm_context_limit_reachedInput exceeds the model's context window
402insufficient_creditsNot enough credits to invoke the model
500internal_errorUnexpected server error
502llm_api_errorModel provider or upstream model API error
502llm_empty_responseModel returned an empty response
502llm_reasoning_limit_reachedModel reached its reasoning limit without a response
502llm_generation_limit_reachedModel reached its generation limit without a response
503service_unavailableService temporarily unavailable

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 until the run ends. Runs are limited to 120 minutes. Non-streaming message requests wait up to 90 seconds, then return 202 Accepted while the run continues. For non-streaming POST /invoke, see Invoke Model.

Connection Headers

Message streams set 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. POST /invoke streams do not set X-Run-Id; use the id in stream_started.

Reconnect and Recovery

SSE streams are not replayed. If the connection drops on a persistent request, 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 status: "ended" or 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:

FieldDescription
session_idSession ID. Omitted for ephemeral streams. Capture this from stream_started for follow-up requests when using persistent POST /messages
run_idCurrent run ID, when available
idInvoke request ID (prefixed with inv_). Only present on POST /invoke streams, which have no session_id or run_id

stream_started

Sent once when the run starts.

FieldDescription
agentSession 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}
FieldDescription
chunk_contentText chunk
seqMonotonic 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.

FieldDescription
contentThinking 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.

Set include_progress: false on a message request to omit only stream_progress. Lifecycle events, ping, message_completed, errors, and terminal events are still sent when relevant.

FieldDescription
stateActivity state: running, success, failed, or skipped. Handle unknown values gracefully
labelHuman-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":"my-support-agent"},"type":"external"}
FieldDescription
message_idCompleted message ID
sequenceOrdering number within the session
fromSender object with type (user or agent) and name
typeMessage 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.

FieldDescription
connection_server_listMCP servers that need a connection action. Each server includes mcp_server_id, mcp_server_name, and optional auth_url
user_action_deadline_unixUnix 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.

FieldDescription
messageHuman-readable non-fatal warning
codeOptional warning code

stream_error

Sent when a run fails. A final stream_ended event may follow with success: false and stop_reason: "error".

FieldDescription
codeError code
messageHuman-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"}}

Cancelled run example:

event: stream_ended
data: {"session_id":"ses_abc123","run_id":"run_abc123","success":false,"stop_reason":"user_cancelled","usage":{"cost":"0.0012"}}
FieldDescription
successWhether the run completed successfully
stop_reasonTerminal run reason: end_run, user_cancelled, user_action_timeout, or error
usageUsage object for this request. cost is always present and is null when the cost is unavailable

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 end 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, or 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, check the run 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 end user and agent, include mcp_server_id and mute. mute accepts a duration such as 30m, 24h, or 7d, or forever.

Request body

FieldTypeRequiredDescription
run_idstringYesActive run ID from the mcp_connection event
mcp_server_idstringNoRequired with mute; use the mcp_server_id from the event
mutestringNoA duration such as 30m, 24h, or 7d, or forever

Response 204 No Content

The stream emits mcp_connection_resolved, then the run continues with the same run_id.

Errors

StatusCodeCondition
400invalid_payloadMalformed JSON or a field has an invalid type
400bad_requestMissing run_id, no active run, or no MCP connection to skip
400bad_requestInvalid mute, missing mcp_server_id when mute is set, or mcp_server_id is not valid for this session
404session_not_foundSession does not exist
404session_not_ownedSession belongs to another developer or does not match the supplied user
409stale_runrun_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

OperationCost
Create Session$0.01 flat for every persistent session created by POST /sessions or POST /messages
Fork Session$0.05 flat, with no separate session creation fee
Send MessageVariable (see below)
Invoke ModelVariable, based on model and tokens

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 POST /messages, POST /sessions/{session_id}/messages, and POST /invoke request places a $0.50 hold on your credit balance before execution begins. This reserves funds for the request. A message queued into an active run (202 Accepted with status: "queued") does not create an additional hold; the active run's usage is checked against your remaining balance.

When the request completes, the hold is settled to the actual cost and the difference is released. If the actual cost exceeds the hold, the difference is also charged, so your balance can go below zero. Cancelled and failed requests are charged only for usage already incurred. If a request fails before reaching the model, the full hold is released.

Because of holds, your available balance may temporarily appear lower during in-flight requests. You need at least $0.50 in available balance for a POST /sessions/{session_id}/messages, ephemeral POST /messages, or POST /invoke request. A persistent POST /messages request also creates a session and needs at least $0.51 to cover the hold plus the session creation fee.


Rate Limits

Every developer account is subject to three rate limit dimensions:

DimensionDefaultDescription
RPM (Requests Per Minute)200Fixed 60-second window
RPD (Requests Per Day)10,000Fixed 24-hour window
Concurrent10Maximum simultaneous in-flight requests and active runs

A run keeps its concurrent slot until it ends, even after a 202 Accepted response or a client disconnect. A POST /invoke request keeps its slot until it finishes, even if the client disconnects.

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.

A separate per-IP limit of 1,200 requests per 60 seconds also applies to all requests and returns the same 429 rate_limit_exceeded error.

Response Headers

Authenticated API responses include rate limit headers:

HeaderDescription
X-RateLimit-LimitYour RPM limit
X-RateLimit-RemainingRequests remaining in the current minute window
X-RateLimit-ResetUnix timestamp when the current window resets
Retry-AfterSeconds to wait before retrying. Sent on 429 rate_limit_exceeded and 503 write_freeze_active responses

When the daily limit is exceeded, X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset describe the daily window instead.

When a limit is exceeded, the API returns 429 Too Many Requests:

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Please try again later."
  }
}

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 lang or Accept-Language (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 include a top-level usage object; cost is null when the cost is unavailable.

These errors can occur on any endpoint:

StatusCodeCondition
401invalid_api_keyAPI key is missing, invalid, or revoked
402developer_not_activatedYour developer account is not activated yet. Purchase credits in the dashboard to activate it
403developer_disabledYour developer account is disabled
403forbiddenRequests from this network are blocked
413payload_too_largeRequest body exceeds 1 MiB
429rate_limit_exceededA rate limit was exceeded. See Rate Limits
503write_freeze_activeWrite requests are temporarily disabled for maintenance

Endpoint-specific errors are documented inline under each endpoint.

Run Errors After Start

Run errors surface after a message request has been accepted. 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 StatusCodeCondition
400content_policy_violationThe model provider rejected the request for content policy reasons
400llm_context_limit_reachedInput exceeds the model's context window
404session_not_foundSession was deleted before the run could execute
409busySession became busy or temporarily unavailable before the run could start
413total_image_size_exceededCombined image payload exceeds the model's per-request size limit
500internal_errorUnexpected run failure
500response_interruptedThe run was interrupted. Send the message again
500response_interrupted_message_savedThe run was interrupted after your message was saved. Fetch the session's messages before sending again
502llm_api_errorModel provider or upstream model API error
502llm_empty_responseModel returned an empty response
502llm_reasoning_limit_reachedModel reached its reasoning limit without a response
502llm_generation_limit_reachedModel reached its generation limit without a response

Pagination

List Sessions and List Messages use cursor-based pagination:

{
  "items": [],
  "next_cursor": "2026-05-19T11:15:00Z|ses_abc123",
  "has_more": true
}
ParameterTypeDefaultMaxDescription
limitinteger20100Number of items per page
cursorstring--Opaque cursor from a previous next_cursor

Pass next_cursor as the cursor query parameter to fetch the next page, URL-encoding the value. When has_more is false, there are no more results. A limit outside 1-100 or an invalid cursor returns 400 bad_request.


Localization

All endpoints accept an optional lang query parameter to control the language of error messages and any localized content.

SourcePriorityExample
lang query parameterHighest?lang=zh
Accept-Language headerFallbackAccept-Language: ja
DefaultLowestEnglish (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