# Agents API
Source: https://docs.junis.ai/api-reference/agents
List and invoke AI agents directly without orchestration
## Overview
The Agents API provides direct access to individual AI agents in your organization. Unlike the Chat Completions API which routes requests through an orchestrator, this API allows you to invoke specific agents directly for targeted tasks.
**Direct Agent Invocation**: Use this API when you know exactly which agent should handle your request. For intelligent routing across multiple agents, use the [Chat Completions API](/api-reference/chat-completions) instead.
Direct invocation skips the orchestrator's routing LLM call, so the first message of a new session starts about 2 seconds earlier. The same can be done on the OpenAI-compatible endpoint with `target_agent_id` — see [Low-latency first turn](/api-reference/chat-completions#low-latency-first-turn-direct-routing-warmup-per-request-context). To avoid the one-time agent build on a new user's first call, use [`POST /v1/warmup`](/api-reference/warmup).
***
## Authentication
All endpoints require API key authentication with specific scopes:
| Endpoint | Required Scope |
| - | - |
| `GET /agents` | `agents:list` |
| `POST /agents/{id}/completions` | `agents:invoke` |
| `POST /agents/{id}/completions` with `on_behalf_of` | `agents:invoke`, `users:delegate` |
**Default Scopes**: The `agents:list` and `agents:invoke` scopes are enabled by default when creating API keys. No additional configuration is needed.
Building your own product on Junis? This endpoint accepts `on_behalf_of` and
`session_hidden`, exactly like [Chat Completions](/api-reference/chat-completions), so
you can run a single agent as one of your users. See
[User Provisioning](/api-reference/user-provisioning).
Include your API key in the `X-API-Key` header:
```bash theme={null}
X-API-Key: jns_live_YOUR_API_KEY_HERE
```
See [Authentication](/api-reference/authentication) for details.
***
## Agent Types
Agents in the system are categorized by their execution pattern:
| Type | Description |
| - | - |
| `LLM_AGENT` | Standard language model agent that processes requests directly |
| `SEQUENTIAL_AGENT` | Executes sub-agents in sequence, passing output between them |
| `PARALLEL_AGENT` | Executes sub-agents simultaneously and combines results |
| `LOOP_AGENT` | Iteratively executes sub-agents based on conditions |
**LLM Agents Only**: Only `LLM_AGENT` type agents can be invoked directly via this API. Sequential, Parallel, and Loop agents are container agents that orchestrate other agents.
***
## List Agents
Retrieve a list of available agents in your organization.
### Endpoint
```
GET https://api.junis.ai/api/external/agents
```
### Query Parameters
| Parameter | Type | Required | Description |
| - | - | - | - |
| `agent_type` | string | No | Filter by type: `LLM_AGENT`, `SEQUENTIAL_AGENT`, `PARALLEL_AGENT`, `LOOP_AGENT` |
| `is_active` | boolean | No | Filter by active status (`true` or `false`) |
### Request Example
```bash cURL theme={null}
curl -X GET "https://api.junis.ai/api/external/agents?agent_type=LLM_AGENT&is_active=true" \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.junis.ai/api/external/agents",
headers={"X-API-Key": "jns_live_YOUR_API_KEY_HERE"},
params={
"agent_type": "LLM_AGENT",
"is_active": True
}
)
print(response.json())
```
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"agents": [
{
"agent_id": "brand-analyzer",
"name": "Brand Analyzer",
"agent_type": "LLM_AGENT",
"description": "Analyzes brand data and generates insights",
"is_active": true,
"model": "anthropic/claude-sonnet-4-6",
"has_tools": true,
"has_mcp": false
},
{
"agent_id": "content-writer",
"name": "Content Writer",
"agent_type": "LLM_AGENT",
"description": "Creates marketing content based on brand guidelines",
"is_active": true,
"model": "anthropic/claude-sonnet-4-6",
"has_tools": false,
"has_mcp": true
}
],
"total": 2,
"organization_id": "org-abc123"
}
```
### Response Fields
| Field | Type | Description |
| - | - | - |
| `agents` | array | List of agent objects |
| `total` | integer | Total number of agents matching the filter |
| `organization_id` | string | Organization ID |
### Agent Object Fields
| Field | Type | Description |
| - | - | - |
| `agent_id` | string | Unique agent identifier |
| `name` | string | Display name of the agent |
| `agent_type` | string | Type of agent (`LLM_AGENT`, `SEQUENTIAL_AGENT`, etc.) |
| `description` | string | Description of the agent's purpose |
| `is_active` | boolean | Whether the agent is currently active |
| `model` | string | LLM model used by the agent (null for non-LLM agents) |
| `has_tools` | boolean | Whether the agent has tools configured |
| `has_mcp` | boolean | Whether the agent has MCP integrations |
***
## Invoke Agent (Non-Streaming)
Send a message to a specific agent and receive the complete response.
### Endpoint
```
POST https://api.junis.ai/api/external/agents/{agent_id}/completions
```
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | - | - |
| `agent_id` | string | **Yes** | The agent ID to invoke |
### Request Body
| Field | Type | Required | Description |
| - | - | - | - |
| `message` | string | **Yes** | The message to send to the agent |
| `session_id` | string | No | Existing session ID to continue conversation |
| `stream` | boolean | No | Set to `false` for non-streaming (default: `false`) |
| `temperature` | number | No | Model temperature (0.0-2.0) |
| `max_tokens` | integer | No | Maximum tokens in response |
| `on_behalf_of` | string | No | Run as a user your organization provisioned. Requires the `users:delegate` scope. Session, message, and memory ownership go to that user; subscription checks and credit charges stay with your API key's own user. |
| `session_hidden` | boolean | No | Keep the session out of user-facing session lists — for background or one-off calls. Applied when a session is created, and updated on reuse only when explicitly passed. |
| `extra_context` | string | No | Per-request context or instructions for **this turn only** (max 65,536 chars). Appended as a non-persistent trailing block after the conversation, together with the user's memory — the same block the orchestrator would receive. Never stored in the session, never merged into the system instruction. |
### Request Example
```bash cURL theme={null}
curl -X POST "https://api.junis.ai/api/external/agents/brand-analyzer/completions" \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"message": "Analyze the brand positioning of Nike",
"stream": false
}'
```
```python Python theme={null}
import requests
response = requests.post(
"https://api.junis.ai/api/external/agents/brand-analyzer/completions",
headers={
"X-API-Key": "jns_live_YOUR_API_KEY_HERE",
"Content-Type": "application/json"
},
json={
"message": "Analyze the brand positioning of Nike",
"stream": False
}
)
print(response.json())
```
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"id": "agentcmpl-550e8400",
"agent_id": "brand-analyzer",
"agent_name": "Brand Analyzer",
"session_id": "sess_abc123def456",
"response": "Nike's brand positioning centers on empowerment and athletic excellence...",
"created": 1767261600,
"model": "anthropic/claude-sonnet-4-6"
}
```
### Response Fields
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique response identifier (format: `agentcmpl-{uuid}`) |
| `agent_id` | string | ID of the agent that processed the request |
| `agent_name` | string | Display name of the agent |
| `session_id` | string | Session ID (can be used for follow-up messages) |
| `response` | string | The agent's complete response |
| `created` | integer | Unix timestamp of response creation |
| `model` | string | LLM model used for the response |
***
## Invoke Agent (Streaming)
Send a message and receive the response as a real-time stream using Server-Sent Events (SSE).
### Endpoint
```
POST https://api.junis.ai/api/external/agents/{agent_id}/completions
```
### Request Body
| Field | Type | Required | Description |
| - | - | - | - |
| `message` | string | **Yes** | The message to send to the agent |
| `session_id` | string | No | Existing session ID to continue conversation |
| `stream` | boolean | **Yes** | Set to `true` for streaming |
| `temperature` | number | No | Model temperature (0.0-2.0) |
| `max_tokens` | integer | No | Maximum tokens in response |
| `on_behalf_of` | string | No | Run as a user your organization provisioned. Requires the `users:delegate` scope. Session, message, and memory ownership go to that user; subscription checks and credit charges stay with your API key's own user. |
| `session_hidden` | boolean | No | Keep the session out of user-facing session lists — for background or one-off calls. Applied when a session is created, and updated on reuse only when explicitly passed. |
| `extra_context` | string | No | Per-request context or instructions for **this turn only** (max 65,536 chars). Appended as a non-persistent trailing block after the conversation, together with the user's memory — the same block the orchestrator would receive. Never stored in the session, never merged into the system instruction. |
### Request Example
```bash cURL theme={null}
curl -X POST "https://api.junis.ai/api/external/agents/brand-analyzer/completions" \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{
"message": "Analyze the brand positioning of Nike",
"stream": true
}'
```
```python Python theme={null}
import requests
response = requests.post(
"https://api.junis.ai/api/external/agents/brand-analyzer/completions",
headers={
"X-API-Key": "jns_live_YOUR_API_KEY_HERE",
"Content-Type": "application/json",
"Accept": "text/event-stream"
},
json={
"message": "Analyze the brand positioning of Nike",
"stream": True
},
stream=True
)
for line in response.iter_lines():
if line:
print(line.decode('utf-8'))
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.junis.ai/api/external/agents/brand-analyzer/completions',
{
method: 'POST',
headers: {
'X-API-Key': 'jns_live_YOUR_API_KEY_HERE',
'Content-Type': 'application/json',
'Accept': 'text/event-stream'
},
body: JSON.stringify({
message: 'Analyze the brand positioning of Nike',
stream: true
})
}
);
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
if (data.type === 'content') {
process.stdout.write(data.content);
}
}
}
}
```
### SSE Response Format
The streaming response uses OpenAI-compatible format with `choices` array:
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique chunk identifier |
| `object` | string | Always `"agent.completion.chunk"` |
| `created` | integer | Unix timestamp |
| `agent_id` | string | Agent ID |
| `agent_name` | string | Agent display name |
| `model` | string | LLM model used |
| `choices` | array | Array containing delta content |
### Streaming Response Example
```
data: {"id": "agentcmpl-abc123", "object": "agent.completion.chunk", "created": 1767261600, "agent_id": "brand-analyzer", "agent_name": "Brand Analyzer", "session_id": "sess_abc123", "model": "anthropic/claude-sonnet-4-6", "choices": [{"index": 0, "delta": {"role": "assistant"}, "finish_reason": null}]}
data: {"id": "agentcmpl-abc123", "object": "agent.completion.chunk", "created": 1767261600, "agent_id": "brand-analyzer", "agent_name": "Brand Analyzer", "model": "anthropic/claude-sonnet-4-6", "choices": [{"index": 0, "delta": {"content": "Nike's "}, "finish_reason": null}]}
data: {"id": "agentcmpl-abc123", "object": "agent.completion.chunk", "created": 1767261600, "agent_id": "brand-analyzer", "agent_name": "Brand Analyzer", "model": "anthropic/claude-sonnet-4-6", "choices": [{"index": 0, "delta": {"content": "brand positioning "}, "finish_reason": null}]}
data: {"id": "agentcmpl-abc123", "object": "agent.completion.chunk", "created": 1767261600, "agent_id": "brand-analyzer", "agent_name": "Brand Analyzer", "model": "anthropic/claude-sonnet-4-6", "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}]}
data: [DONE]
```
### Chunk Object Fields
| Field | Type | Description |
| - | - | - |
| `choices[].index` | integer | Always `0` |
| `choices[].delta.role` | string | `"assistant"` (first chunk only) |
| `choices[].delta.content` | string | Incremental text content |
| `choices[].finish_reason` | string | `null` during streaming, `"stop"` on completion |
***
## Session Continuity
You can maintain conversation context by reusing the `session_id` from previous responses:
```python Python theme={null}
import requests
API_KEY = "jns_live_YOUR_API_KEY_HERE"
BASE_URL = "https://api.junis.ai/api/external/agents"
# First message
response1 = requests.post(
f"{BASE_URL}/brand-analyzer/completions",
headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
json={"message": "Tell me about Nike's brand"}
)
session_id = response1.json()["session_id"]
# Follow-up message using same session
response2 = requests.post(
f"{BASE_URL}/brand-analyzer/completions",
headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
json={
"message": "How does it compare to Adidas?",
"session_id": session_id # Continue conversation
}
)
print(response2.json()["response"])
```
***
## Error Responses
### Common Errors
```json 400 Bad Request - Invalid Agent Type Filter theme={null}
{
"detail": "Invalid agent_type 'unknown'. Must be one of: ['LLM_AGENT', 'SEQUENTIAL_AGENT', 'PARALLEL_AGENT', 'LOOP_AGENT']"
}
```
```json 400 Bad Request - Inactive Agent theme={null}
{
"error": {
"message": "Agent 'brand-analyzer' is not active",
"type": "invalid_request",
"code": "agent_inactive"
}
}
```
```json 402 Payment Required - Insufficient Credits theme={null}
{
"error": {
"message": "크레딧이 부족합니다. 현재 잔액: $0.50",
"message_en": "Insufficient credits. Balance: $0.50",
"type": "insufficient_credits",
"code": "insufficient_credits"
},
"credit_balance": 0.5,
"effective_balance": 0.5
}
```
```json 403 Forbidden - No Subscription theme={null}
{
"error": {
"message": "Basic or Pro subscription required. Please visit /subscription to start a subscription.",
"message_ko": "Basic 또는 Pro 구독이 필요합니다. 구독을 시작하려면 /subscription 페이지를 방문하세요.",
"type": "subscription_required",
"code": "no_subscription"
}
}
```
```json 403 Forbidden - Insufficient Scopes theme={null}
{
"error": "insufficient_scopes",
"message": "API key does not have required permissions",
"required_scopes": ["agents:invoke"],
"missing_scopes": ["agents:invoke"],
"available_scopes": ["orchestrator:invoke", "sessions:read"]
}
```
```json 404 Not Found theme={null}
{
"error": {
"message": "Agent 'invalid-agent-id' not found in organization",
"type": "not_found",
"code": "agent_not_found"
}
}
```
***
## Best Practices
Use the List Agents endpoint to discover available agents and their capabilities:
```python theme={null}
# Get all active LLM agents
response = requests.get(
"https://api.junis.ai/api/external/agents",
headers={"X-API-Key": api_key},
params={"agent_type": "LLM_AGENT", "is_active": True}
)
agents = response.json()["agents"]
for agent in agents:
print(f"{agent['name']}: {agent['description']}")
print(f" - Model: {agent['model']}")
print(f" - Has Tools: {agent['has_tools']}")
print(f" - Has MCP: {agent['has_mcp']}")
```
For streaming responses, implement proper SSE parsing:
```python theme={null}
import json
import requests
response = requests.post(
f"{BASE_URL}/agents/{agent_id}/completions",
headers={"X-API-Key": api_key, "Content-Type": "application/json"},
json={"message": prompt, "stream": True},
stream=True
)
full_response = ""
for line in response.iter_lines():
if line:
line = line.decode('utf-8')
if line.startswith('data: '):
try:
data = json.loads(line[6:])
if data.get('type') == 'content':
full_response += data.get('content', '')
elif data.get('type') == 'done':
break
elif data.get('type') == 'error':
raise Exception(data.get('error'))
except json.JSONDecodeError:
continue
```
Implement comprehensive error handling for production use:
```python theme={null}
def invoke_agent(agent_id, message, api_key):
try:
response = requests.post(
f"https://api.junis.ai/api/external/agents/{agent_id}/completions",
headers={"X-API-Key": api_key, "Content-Type": "application/json"},
json={"message": message},
timeout=120
)
if response.status_code == 200:
return response.json()
elif response.status_code == 402:
raise PaymentError("Subscription or credits required")
elif response.status_code == 403:
raise PermissionError("Insufficient API key permissions")
elif response.status_code == 404:
raise ValueError(f"Agent not found: {agent_id}")
else:
raise Exception(f"API error: {response.json().get('detail')}")
except requests.Timeout:
raise TimeoutError("Request timed out after 120 seconds")
```
Efficiently manage sessions for multi-turn conversations:
```python theme={null}
class AgentSession:
def __init__(self, agent_id, api_key):
self.agent_id = agent_id
self.api_key = api_key
self.session_id = None
def send_message(self, message):
payload = {"message": message}
if self.session_id:
payload["session_id"] = self.session_id
response = requests.post(
f"https://api.junis.ai/api/external/agents/{self.agent_id}/completions",
headers={"X-API-Key": self.api_key, "Content-Type": "application/json"},
json=payload
)
result = response.json()
self.session_id = result.get("session_id")
return result["response"]
# Usage
session = AgentSession("brand-analyzer", api_key)
print(session.send_message("Tell me about Nike"))
print(session.send_message("Compare it to Adidas")) # Continues conversation
```
***
## Related Resources
Learn about API key scopes and authentication
Use orchestrated multi-agent conversations
Manage and retrieve conversation sessions
Full list of error codes and solutions
# Authentication
Source: https://docs.junis.ai/api-reference/authentication
Secure your API requests with API keys
## Overview
The Junis External API uses **API Keys** for authentication. All API requests must include a valid API key in the `X-API-Key` header.
Junis has two kinds of API keys: **organization keys** (`jns_live_`) bound to a single
organization, and **user keys** (`jns_user_`) that work across all organizations you
belong to. See [Organization vs User API Keys](#organization-vs-user-api-keys) below.
***
## API Key Authentication
### Creating an API Key
Go to **Dashboard** → **API Keys** in the Junis dashboard
Click the **"+ Create API Key"** button
Fill in the form:
* **Name**: A descriptive name for the key (e.g., "Production API", "Testing")
* **Description**: Optional notes about the key's purpose
* **Scopes**: Select permissions (default: all external API scopes enabled)
* **Rate Limits**: Set automatically (100 requests per minute, 3,000 per hour per key); an admin can change them later through the admin API
**IMPORTANT**: Copy the API key and store it securely. Treat it like a password —
anyone with the key can act with its permissions.
The key format is: `jns_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` (32 random characters)
### API Key Format
| Prefix | Purpose | Where to Create |
| - | - | - |
| `jns_live_` | **Organization key** — bound to one organization | **Admin → API Keys** (admin/owner) |
| `jns_user_` | **User key** — works across all your organizations | **Settings → API Keys** (up to 5 per user) |
### Organization vs User API Keys
* **Organization keys** (`jns_live_`) always act within the organization they were created in.
* **User keys** (`jns_user_`) act as *you*, across every organization you belong to. Select
which organization a request targets with the `X-Organization-Id` header; if omitted, your
current organization is used.
```bash cURL — user key targeting a specific organization theme={null}
curl -X POST https://api.junis.ai/api/external/v1/chat/completions \
-H "X-API-Key: jns_user_YOUR_API_KEY_HERE" \
-H "X-Organization-Id: YOUR_ORG_ID" \
-H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "Hello!"}]}'
```
User keys are what [Junis Magic](/guides/junis-magic) uses — one key, all your squads.
***
## Using API Keys
Include your API key in the `X-API-Key` header:
```bash cURL theme={null}
curl -X POST https://api.junis.ai/api/external/v1/chat/completions \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role": "user", "content": "Hello!"}
]
}'
```
***
## API Key Permissions (Scopes)
### Available Scopes
API keys can have granular permissions. Each scope grants access to specific API operations:
| Scope | Description | Required For |
| - | - | - |
| `orchestrator:invoke` | Invoke orchestrator with prompts (non-streaming) | Chat Completions (non-streaming) |
| `orchestrator:stream` | Stream orchestrator responses (SSE) | Chat Completions with `stream: true` |
| `sessions:read` | Read chat sessions | List sessions, Get session status |
| `sessions:messages` | Read session messages | Get session messages |
| `content:read` | Read generated content | List content, Get content detail |
| `content:retry` | Retry failed content generation | Retry content generation |
| `agents:list` | List available agents in organization | List agents |
| `agents:invoke` | Invoke specific agents directly | Agent completions |
| `rag:upload` | Upload files to RAG Datastore | Create DataStore, Upload files |
| `rag:read` | Read RAG uploads and DataStores | List DataStores, List uploads, Get upload status |
| `rag:delete` | Delete RAG uploads and documents (opt-in, never default) | Delete RAG resources |
| `sessions:delete` | Delete a session and its messages (opt-in, never default) | Delete a session |
| `admin:read` | Read admin resources (organization-scoped) | Admin endpoints (if accessible) |
| `admin:write` | Write admin resources (organization-scoped) | Admin endpoints (if accessible) |
| `users:provision` | Provision (create-or-get) users and pending org memberships | [User Provisioning](/api-reference/user-provisioning) |
| `users:delegate` | Act on behalf of another organization member | `on_behalf_of` on chat/agent completions and on session read & delete |
**Default Scopes**: Organization keys created from the dashboard include:
`orchestrator:invoke`, `orchestrator:stream`, `sessions:read`, `sessions:messages`,
`agents:list`, `agents:invoke`, `content:read`, `content:retry`.
User keys include a broader default set covering management operations (used by Junis Magic).
Additional scopes — RAG and delete — are granted by updating the key
(ask your organization admin). For partner scopes, see below.
**Updating scopes replaces the whole list.** Send every scope the key should keep, not
just the ones you are adding. Unknown scope names are rejected so a typo cannot leave a
key permanently locked out, and an empty list is refused unless you explicitly confirm
it — to disable a key, deactivate it instead of stripping its scopes.
**Partner scopes are opt-in only**: `users:provision`, `users:delegate` and
`sessions:delete` are **never** included in any default scope set, and are intended
for trusted partner services that manage end-user accounts.
To get them, create an organization key from **Dashboard → API Keys → New Key** with
**"Connect users from my own app"** ticked. A key created without that checkbox does
not carry them. See [User Provisioning](/api-reference/user-provisioning).
### Scope Requirements by Endpoint
| Endpoint | Required Scopes |
| - | - |
| `POST /api/external/v1/chat/completions` (non-streaming) | `orchestrator:invoke` |
| `POST /api/external/v1/chat/completions` (streaming) | `orchestrator:invoke`, `orchestrator:stream` |
| `GET /api/external/sessions` | `sessions:read` |
| `GET /api/external/sessions/{id}/messages` | `sessions:messages` |
| `GET /api/external/sessions/{id}/status` | `sessions:read` |
| `GET /api/external/content` | `content:read` |
| `GET /api/external/content/{id}` | `content:read` |
| `POST /api/external/content/{id}/retry` | `content:retry` |
| `GET /api/external/agents` | `agents:list` |
| `POST /api/external/agents/{id}/completions` (non-streaming) | `agents:invoke` |
| `POST /api/external/agents/{id}/completions` (streaming) | `agents:invoke` |
| `POST /api/external/rag/datastores` | `rag:upload` |
| `GET /api/external/rag/datastores` | `rag:read` |
| `POST /api/external/rag/upload` | `rag:upload` |
| `POST /api/external/management/users` | `users:provision` |
| `POST /api/external/v1/chat/completions` with `on_behalf_of` | `orchestrator:invoke`, `users:delegate` |
| `GET /api/external/sessions` with `on_behalf_of` | `sessions:read`, `users:delegate` |
| `GET /api/external/sessions/{id}/messages` with `on_behalf_of` | `sessions:messages`, `users:delegate` |
| `DELETE /api/external/sessions/{id}` | `sessions:delete` |
| `DELETE /api/external/sessions/{id}` with `on_behalf_of` | `sessions:delete`, `users:delegate` |
| `GET /api/external/rag/uploads` | `rag:read` |
| `GET /api/external/rag/uploads/{id}` | `rag:read` |
***
## Rate Limits
API keys are subject to rate limits to ensure fair usage and system stability.
### Default Rate Limits
| Limit | Organization key | Personal key |
| - | - | - |
| Requests per minute, per key | 100 | 100 |
| Requests per hour, per key | 3,000 | 2,000 |
| Requests per minute, whole organization (all keys combined) | 300 | 300 |
Per-key limits can be adjusted by an organization admin through the admin API (`PUT /api/admin/api-keys/{key_id}`); the organization-wide total is a platform setting.
### Rate Limit Headers
All API responses include rate limit information in headers:
```http theme={null}
X-RateLimit-Limit-Minute: 100
X-RateLimit-Remaining-Minute: 85
X-RateLimit-Reset-Minute: 1767261600
X-RateLimit-Limit-Hour: 3000
X-RateLimit-Remaining-Hour: 2950
X-RateLimit-Reset-Hour: 1767265200
X-RateLimit-Limit-Org-Minute: 300
X-RateLimit-Remaining-Org-Minute: 240
```
See [Rate Limits](/api-reference/rate-limits) for detailed documentation.
***
## Security Best Practices
* **Never commit API keys to Git repositories**
* Store keys in environment variables or secret management systems (e.g., AWS Secrets Manager, HashiCorp Vault)
* Use `.env` files for local development (add to `.gitignore`)
* Rotate keys regularly (every 90 days recommended)
* All API requests must use HTTPS
* Never send API keys over unencrypted HTTP
* Our API automatically rejects non-HTTPS requests
* Create separate keys for development, staging, and production
* Use descriptive names to identify key purposes (e.g., "Prod - Web App", "Dev - Testing")
* Revoke unused keys immediately
* Check the API Keys page for usage statistics
* Review `last_used_at` and `request_count` regularly
* Set up alerts for unusual activity (e.g., sudden spike in requests)
If an API key is compromised:
1. **Immediately revoke** the key in Admin → API Keys
2. **Create a new key** with a different name
3. **Update all applications** to use the new key
4. **Review logs** for suspicious activity (unusual endpoints, high request counts)
* Only grant scopes that are actually needed
* For read-only integrations, use `sessions:read` and `sessions:messages` only
* Avoid granting `admin:write` unless absolutely necessary
***
## Error Handling
### Common Authentication Errors
```json 401 Unauthorized - Missing API Key theme={null}
{
"detail": "API key required. Include X-API-Key header with your request."
}
```
```json 401 Unauthorized - Invalid API Key theme={null}
{
"detail": "Invalid or expired API key"
}
```
```json 403 Forbidden - Insufficient Scopes theme={null}
{
"error": "insufficient_scopes",
"message": "API key does not have required permissions",
"required_scopes": ["sessions:delete"],
"missing_scopes": ["sessions:delete"],
"available_scopes": ["orchestrator:invoke", "sessions:read"]
}
```
```json 429 Too Many Requests theme={null}
{
"error": {
"type": "rate_limit_error",
"message": "Rate limit exceeded. Please try again in 60 seconds.",
"code": "rate_limit_exceeded",
"retry_after": 60
}
}
```
### Handling Errors in Code
When you receive an error response, check the HTTP status code and error message to determine the cause. Common error codes:
* **401 Unauthorized**: Invalid or missing API key
* **403 Forbidden**: Insufficient scopes/permissions
* **429 Too Many Requests**: Rate limit exceeded (wait for `retry_after` seconds)
***
## Next Steps
Start sending messages to your agents
Retrieve and manage chat sessions
Understand rate limiting and best practices
Full list of error codes and solutions
# Chat Completions
Source: https://docs.junis.ai/api-reference/chat-completions
Send messages to your Junis orchestrator with OpenAI-compatible API
## Overview
The Chat Completions API is **fully compatible with OpenAI's API format**, allowing you to use the OpenAI Python SDK, JavaScript SDK, or any OpenAI-compatible client.
**Model Parameter (Optional)**: The `model` parameter is optional and primarily for SDK compatibility. Your organization's orchestrator agent automatically uses the configured LLM model.
If you're using an OpenAI SDK that requires the `model` parameter, you can pass any value (e.g., `"junis-orchestrator"`) for compatibility.
***
## Prerequisites
**Subscription Required**: This API is only available for **Basic** or **Pro** subscribers.
If you don't have an active subscription, you'll receive a `403 Forbidden` error. Visit [junis.ai/subscription](https://junis.ai/subscription) to start a plan.
***
## Endpoint
```
POST https://api.junis.ai/api/external/v1/chat/completions
```
### Authentication
Include your API key in the `X-API-Key` header:
```bash theme={null}
X-API-Key: jns_live_YOUR_API_KEY_HERE
```
**Required Scope:** `orchestrator:invoke` (`orchestrator:stream` for `stream: true`, `agents:invoke` when `target_agent_id` is set)
See [Authentication](/api-reference/authentication) for details.
***
## Request Format
### Basic Request
```bash cURL theme={null}
curl -X POST https://api.junis.ai/api/external/v1/chat/completions \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role": "user", "content": "What is Junis?"}
]
}'
```
### Request Parameters
| Parameter | Type | Required | Description |
| - | - | - | - |
| `messages` | array | **Yes** | Array of message objects (OpenAI format) |
| `stream` | boolean | No | Enable streaming mode (default: `false`) |
| `session_id` | string | No | Session ID to continue conversation (creates new if not provided) |
| `on_behalf_of` | string | No | Act on behalf of this Junis user ID. Requires the `users:delegate` scope, and the target must be an active or pending member of the key's organization. Session/message/memory ownership goes to the target user; **subscription checks and credit charges stay with the API key's backing user**. Omitted → behavior identical to today. |
| `session_hidden` | boolean | No | Create the session as hidden (or update the flag when explicitly passed while reusing a session you own). Hidden sessions are excluded from user-facing session lists (web sidebar, admin session list) but remain fetchable by `session_id`. Omitted → sessions are visible as today. |
| `extra_context` | string | No | Per-request context or instructions for **this turn only** (max 65,536 chars). Delivered as a non-persistent trailing block to the orchestrator **and to the agent that answers** — never stored in the session, never merged into the system instruction. Omitted → behavior identical to today. |
| `target_agent_id` | string | No | Route this turn **directly** to the given agent (UUID of an active agent in your organization, normally a sub-agent of the orchestrator) and skip the orchestrator's routing LLM call. Requires the `agents:invoke` scope. Unknown or foreign id → `404 agent_not_found`; inactive → `400 agent_inactive`. Later turns may omit it — the session continues with the agent that answered last. See [Low-latency first turn](#low-latency-first-turn-direct-routing-warmup-per-request-context). |
**Delegation (`on_behalf_of`)** is designed for partner services that provision
end-user accounts via [User Provisioning](/api-reference/user-provisioning).
Pending members are allowed so provisioned users work before their first login.
### Message Format
Messages follow the OpenAI format:
```json theme={null}
{
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
{"role": "assistant", "content": "Hi! How can I help you?"},
{"role": "user", "content": "Tell me about Junis."}
]
}
```
**Role Types:**
* `system`: System instructions (optional, rarely needed with Junis)
* `user`: User messages
* `assistant`: Assistant responses (for conversation context)
* `tool`: Tool call results (for function calling)
***
## Response Format
### Non-Streaming Response
**Status Code:** `200 OK`
**Response Body:**
```json theme={null}
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"created": 1767261600,
"model": "junis-orchestrator",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Junis is a production-ready Multi-Agent AI platform..."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 15,
"completion_tokens": 42,
"total_tokens": 57
},
"session_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
### Response Fields
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique completion ID |
| `object` | string | Object type (always `"chat.completion"`) |
| `created` | integer | Unix timestamp |
| `model` | string | Model used |
| `choices` | array | Array of completion choices |
| `choices[].message` | object | Generated message |
| `choices[].message.role` | string | Always `"assistant"` |
| `choices[].message.content` | string | Response text |
| `choices[].finish_reason` | string | Reason for completion (`stop`, `length`, `tool_calls`, `content_filter`) |
| `usage` | object | Token usage statistics |
| `session_id` | string | Session ID for continuing the conversation |
***
## Streaming Mode
Enable real-time token-by-token streaming with `stream: true`.
### Streaming Request
```bash cURL theme={null}
curl -N -X POST https://api.junis.ai/api/external/v1/chat/completions \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "Tell me a story"}],
"stream": true
}'
```
### Streaming Response Format
Server-Sent Events (SSE) format:
```
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1767261600,"model":"junis-orchestrator","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}],"session_id":"550e8400-e29b-41d4-a716-446655440000"}
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1767261600,"model":"junis-orchestrator","choices":[{"index":0,"delta":{"content":"Once"},"finish_reason":null}],"session_id":"550e8400-e29b-41d4-a716-446655440000"}
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1767261600,"model":"junis-orchestrator","choices":[{"index":0,"delta":{"content":" upon"},"finish_reason":null}],"session_id":"550e8400-e29b-41d4-a716-446655440000"}
...
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1767261600,"model":"junis-orchestrator","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"session_id":"550e8400-e29b-41d4-a716-446655440000"}
data: [DONE]
```
**Key Points:**
* Each line starts with `data: `
* First chunk contains `role`
* Subsequent chunks contain `content` deltas
* Final chunk has `finish_reason`
* **Every chunk includes `session_id`** for conversation continuity
* Stream ends with `data: [DONE]`
* Lines starting with `:` (for example `: keepalive`) are SSE comments sent every 15 seconds while the server is still preparing the answer (including the first-time build of your organization's agents). Standard SSE/OpenAI clients ignore them.
***
## Low-latency first turn: direct routing, warmup, per-request context
The first message of a **new session** normally costs one extra LLM call: the orchestrator reads the request and decides which agent should answer (`transfer_to_agent`). Measured in production this routing call takes about 2 seconds (p50) before the answering agent even starts. Later turns in the same session skip it — Junis resumes the agent that answered last.
If your application already knows which agent should answer (for example a fixed "counselor" or "support" agent), you can skip the routing call on the first turn too.
### `target_agent_id` — skip the routing call
```bash cURL theme={null}
curl -N -X POST https://api.junis.ai/api/external/v1/chat/completions \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "My child had a hard time at school today"}],
"on_behalf_of": "9f1c…",
"extra_context": "Child: 7 years old, recently changed schools",
"target_agent_id": "785aba85-caf1-4e22-8ca9-841093e611d6",
"stream": true
}'
```
| Rule | Detail |
| - | - |
| Value | UUID of an **active** agent in your organization (use the `agent_id` from [`GET /agents`](/api-reference/agents)). Normally a sub-agent of your orchestrator. |
| Scope | `agents:invoke` in addition to `orchestrator:invoke` (the same permission as [`POST /agents/{agent_id}/completions`](/api-reference/agents)). Default keys include both. |
| Validation | Happens **before** the session is created, so a wrong id never leaves an empty session behind. Unknown/foreign/global id → `404 agent_not_found`, inactive → `400 agent_inactive`. |
| What the agent receives | On a `target_agent_id` turn the target agent acts as the orchestrator for that turn, so it receives the trailing block the orchestrator would have received: the user's **memory** plus `extra_context`. |
| Later turns | You may omit the field — the session continues with the agent that answered last. Sending it on every turn gives the same result. |
| Edge cases | If the target is not part of the orchestrator's tree, the next turn without the field is routed by the orchestrator again. If the last responder is a child of a Sequential/Parallel/Loop agent, every turn is routed by the orchestrator (ADK rule). |
`POST /agents/{agent_id}/completions` gives the same effect (no routing call). `target_agent_id` lets you keep the OpenAI-compatible body, `files`, and session rules of this endpoint.
### `extra_context` — context for this turn only
`extra_context` is appended as a **non-persistent trailing block** after the conversation, for this request only. It is never written to the session and never merged into the system instruction, so it cannot pollute later turns or break prompt caching. Since October 2026 it is delivered to the agent that actually answers (orchestrator-routed sub-agent, `target_agent_id` target, or `/agents/{agent_id}/completions`), not only to the orchestrator.
* Maximum 65,536 characters (`422` if exceeded).
* It counts as uncached input tokens on every call that receives it — send what the turn needs, not your whole database.
* If your organization prefers the previous behavior (context on the orchestrator call only), contact us and we will turn off delivery to the answering agent for your organization. Sub-agent requests then match the pre-October behavior byte for byte. Note that on `target_agent_id` turns the orchestrator does not run, so with delivery turned off no model call receives `extra_context` (or the user's memory) on those turns — the same as before October.
### Warmup — build the agents before the first message
Your organization's agents are built lazily per (organization, user) on the server that receives the request, so the first message of a newly provisioned user pays that build (fractions of a second for small squads, several seconds for squads with many tools). Call [`POST /v1/warmup`](/api-reference/warmup) right after provisioning the user and the first message finds everything ready.
***
## Multi-Agent Events
**For Advanced Integrations**: Junis streams additional agent lifecycle events alongside standard OpenAI chunks. These events have no `choices` field, so **standard OpenAI SDKs automatically ignore them** - ensuring 100% backward compatibility.
Parse these events to build rich UIs that show which agents are processing your request.
### Event Types
| Event | Description | Fields |
| - | - | - |
| `agent_started` | Agent begins processing | `agent`, `agent_type`, `depth`, `parent_agent` |
| `agent_completed` | Agent finishes processing | `agent`, `duration_ms` |
| `agent_routing` | Transition between agents | `from_agent`, `to_agent` |
| `token_agent` | Which agent produced the token | `agent` |
| `tool_started` | Tool execution begins | `tool_name`, `tool_id`, `agent_name` |
| `tool_completed` | Tool execution ends | `tool_name`, `tool_id`, `agent_name` |
| `payment_required` | Payment needed to continue | `message`, `agent_name`, `nonce`, `network`, `total_amount` |
### SSE Stream Example (Multi-Agent)
```
data: {"event":"agent_started","agent":"researcher","agent_type":"LLM_AGENT","depth":1,"timestamp":"2026-01-01T10:00:00Z"}
data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":"Based on"}}],"session_id":"..."}
data: {"event":"token_agent","agent":"researcher","timestamp":"2026-01-01T10:00:00Z"}
data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":" my research"}}],"session_id":"..."}
data: {"event":"agent_completed","agent":"researcher","duration_ms":1523,"timestamp":"2026-01-01T10:00:01Z"}
data: {"event":"agent_routing","from_agent":"researcher","to_agent":"writer","timestamp":"2026-01-01T10:00:01Z"}
data: {"event":"agent_started","agent":"writer","agent_type":"LLM_AGENT","depth":1,"timestamp":"2026-01-01T10:00:01Z"}
...
data: [DONE]
```
### Parsing Agent Events (Python)
```python theme={null}
import json
import httpx
async def stream_with_agent_events(prompt: str, api_key: str):
"""Stream response and track agent events."""
async with httpx.AsyncClient() as client:
async with client.stream(
"POST",
"https://api.junis.ai/api/external/v1/chat/completions",
headers={"X-API-Key": api_key},
json={"messages": [{"role": "user", "content": prompt}], "stream": True}
) as response:
current_agent = None
agent_durations = {}
async for line in response.aiter_lines():
if not line.startswith("data: "):
continue
data_str = line[6:]
if data_str == "[DONE]":
break
data = json.loads(data_str)
# Agent event (no 'choices' field)
if "event" in data and "choices" not in data:
event = data["event"]
if event == "agent_started":
current_agent = data["agent"]
print(f"🚀 Agent started: {current_agent}")
elif event == "agent_completed":
agent = data["agent"]
duration = data.get("duration_ms", 0)
agent_durations[agent] = duration
print(f"✅ Agent completed: {agent} ({duration}ms)")
elif event == "agent_routing":
print(f"🔄 Routing: {data['from_agent']} → {data['to_agent']}")
# Standard OpenAI chunk (has 'choices' field)
elif "choices" in data:
content = data["choices"][0].get("delta", {}).get("content", "")
if content:
print(content, end="", flush=True)
print(f"\n\n📊 Agent performance: {agent_durations}")
```
### Parallel Agent Support
When multiple agents run concurrently (Parallel Agent), use `token_agent` events to correctly attribute tokens:
```python theme={null}
agent_buffers = {} # Per-agent content buffers
last_token_agent = None
for event in stream:
if event.get("event") == "token_agent":
last_token_agent = event["agent"]
elif "choices" in event:
content = event["choices"][0].get("delta", {}).get("content", "")
if content and last_token_agent:
if last_token_agent not in agent_buffers:
agent_buffers[last_token_agent] = ""
agent_buffers[last_token_agent] += content
# Result: {"researcher": "Research content...", "analyst": "Analysis content..."}
```
**When to use agent events:**
* Build agent pipeline visualizations
* Track per-agent performance metrics
* Handle Parallel Agent outputs separately
* Debug multi-agent workflows
***
## Session Management
Junis supports explicit session management via `session_id` parameter, allowing you to control conversation context precisely.
### How Sessions Work
```mermaid theme={null}
sequenceDiagram
participant Client
participant API
participant Database
Client->>API: First message (no session_id)
API->>Database: Create new session
Database-->>API: session_id
API-->>Client: Response with session_id
Client->>API: Follow-up message (with session_id)
API->>Database: Load existing session
Database-->>API: session context
API-->>Client: Contextual response with session_id
```
### Session ID Usage
**Recommended**: Always capture the `session_id` from the first response and include it in subsequent requests to maintain conversation context.
**Behavior:**
| Request | Result |
| - | - |
| No `session_id` | Creates a **new session** and returns `session_id` in response |
| With `session_id` (existing) | Uses the **existing session** (same chat room) |
| With `session_id` (new ID) | Creates a **new session** with the provided ID |
### Example: Continuing a Conversation
```python theme={null}
import requests
API_KEY = "jns_live_YOUR_API_KEY_HERE"
BASE_URL = "https://api.junis.ai/api/external/v1/chat/completions"
# First request - no session_id
response1 = requests.post(BASE_URL,
headers={"X-API-Key": API_KEY},
json={"messages": [{"role": "user", "content": "Hello!"}]}
)
session_id = response1.json()["session_id"] # Capture session_id
# Second request - with session_id (continues conversation)
response2 = requests.post(BASE_URL,
headers={"X-API-Key": API_KEY},
json={
"messages": [{"role": "user", "content": "What did I just say?"}],
"session_id": session_id # Use captured session_id
}
)
# Response will have context from the first message
```
### Viewing Sessions
Retrieve your sessions via the Sessions API:
```bash theme={null}
curl -X GET https://api.junis.ai/api/external/sessions \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE"
```
See [Sessions API](/api-reference/sessions) for details.
***
## Function Calling (Tool Use)
**Current Implementation**: Tool calling uses keyword pattern matching, not LLM-native function calling.
Supported tools:
* `end_call`: Detects phrases like "안녕히", "감사합니다", "goodbye"
* `transfer_call`: Detects phrases like "상담원 연결", "전화 연결", "connect agent"
Full LLM-native function calling support is in development.
When a supported keyword is detected in the assistant's response, the API returns a `tool_calls` object with `finish_reason: "tool_calls"`.
***
## Error Handling
### Common Errors
```json 400 Bad Request theme={null}
{
"error": {
"message": "Invalid request: 'messages' field is required",
"type": "invalid_request_error",
"param": "messages",
"code": "missing_required_field"
}
}
```
```json 401 Unauthorized theme={null}
{
"error": {
"message": "Invalid API key",
"type": "authentication_error",
"code": "invalid_api_key"
}
}
```
```json 402 Payment Required theme={null}
{
"error": {
"message": "Insufficient credits. Current balance: $-5.00. Please add credits to continue.",
"type": "insufficient_credits",
"code": "insufficient_credits"
},
"credit_balance": -5.0,
"effective_balance": -5.0,
"can_use_api": false
}
```
```json 403 Forbidden (No Subscription) theme={null}
{
"error": {
"message": "Basic or Pro subscription required. Please visit /subscription to start a subscription.",
"type": "subscription_required",
"code": "no_subscription"
},
"has_subscription": false,
"required_plans": ["basic_monthly", "pro_monthly"]
}
```
```json 403 Forbidden (Invalid Plan) theme={null}
{
"error": {
"message": "External API is only available for Basic or Pro subscribers.",
"type": "invalid_subscription_plan",
"code": "invalid_plan"
},
"current_plan": "token_30",
"required_plans": ["basic_monthly", "pro_monthly"]
}
```
```json 429 Too Many Requests (Minute Limit) theme={null}
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded: 60 requests per minute",
"limit": 60,
"window": "minute",
"reset_at": 1767261600,
"retry_after": 45
}
```
```json 429 Too Many Requests (Hour Limit) theme={null}
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded: 3000 requests per hour",
"limit": 3000,
"window": "hour",
"reset_at": 1767265200,
"retry_after": 1800
}
```
```json 500 Internal Server Error theme={null}
{
"error": {
"message": "Failed to process request: Internal error",
"type": "api_error",
"code": "internal_error"
}
}
```
### Error Types
| Error Type | Status Code | Description | Action |
| - | - | - | - |
| `invalid_request_error` | 400 | Malformed request | Check request format |
| `authentication_error` | 401 | Invalid API key | Verify API key |
| `insufficient_credits` | 402 | Credit balance below zero | Add credits at /subscription |
| `subscription_required` | 403 | No active subscription | Subscribe to Basic or Pro plan |
| `invalid_subscription_plan` | 403 | Plan doesn't include API access | Upgrade to Basic or Pro plan |
| `rate_limit_error` | 429 | Too many requests | Implement exponential backoff |
| `api_error` | 500 | Server error | Retry with exponential backoff |
***
## Best Practices
* **Use Streaming**: Enable `stream: true` for long responses to provide real-time feedback
* **Handle Rate Limits**: Implement exponential backoff when hitting rate limits (see error codes above)
* **Keep Message History Concise**: Trim old messages to avoid token limits (keep last 10-20 messages)
* **Secure API Keys**: Never hardcode API keys; use environment variables
* **Log Requests**: Log requests and responses for debugging and monitoring
***
## Response Parameters
### Temperature Guide
| Use Case | Recommended Temperature |
| - | - |
| Factual Q\&A | 0.0 - 0.3 |
| General chat | 0.5 - 0.7 |
| Creative writing | 0.8 - 1.0 |
| Code generation | 0.0 - 0.2 |
***
## Next Steps
Retrieve and manage conversation sessions
Complete error reference
Understand rate limiting
# Generated Content
Source: https://docs.junis.ai/api-reference/content
Access and manage AI-generated content (images, videos, audio, 3D models)
## Overview
The Generated Content API provides access to content generated through Junis AI agents using [Replicate](https://replicate.com) models. This includes images, videos, audio files, and 3D models (GLB format).
**Content Generation**: Content is generated through AI agents that use the `gen_content` tool. This API provides read-only access to view and retry generated content.
***
## Authentication
All endpoints require API key authentication with specific scopes:
| Endpoint | Required Scope |
| - | - |
| `GET /content` | `content:read` |
| `GET /content/{id}` | `content:read` |
| `POST /content/{id}/retry` | `content:retry` |
**Default Scopes**: The `content:read` and `content:retry` scopes are **not** enabled by default when creating API keys. You must explicitly enable these scopes during key creation.
Include your API key in the `X-API-Key` header:
```bash theme={null}
X-API-Key: jns_live_YOUR_API_KEY_HERE
```
See [Authentication](/api-reference/authentication) for details.
***
## Content Types
Generated content is categorized into the following types:
| Type | Description | Example Models |
| - | - | - |
| `image` | Static images (PNG, JPG, WebP) | SDXL, Flux, Stable Diffusion |
| `video` | Video files (MP4, WebM) | Runway Gen-3, AnimateDiff |
| `audio` | Audio files (MP3, WAV) | MusicGen, Bark |
| `glb` | 3D models (GLB format) | Point-E, Shap-E |
| `other` | Other content types | Various |
***
## Content Status
Content goes through the following status lifecycle:
| Status | Description |
| - | - |
| `pending` | Generation request received, waiting to be processed |
| `processing` | Content is being generated by Replicate |
| `completed` | Generation successful, content available |
| `failed` | Generation failed (can be retried) |
***
## List Content
Retrieve a paginated list of generated content for your organization.
### Endpoint
```
GET https://api.junis.ai/api/external/content
```
### Query Parameters
| Parameter | Type | Required | Description |
| - | - | - | - |
| `page` | integer | No | Page number (default: `1`, min: `1`) |
| `page_size` | integer | No | Items per page (default: `20`, max: `100`) |
| `content_type` | string | No | Filter by type: `image`, `video`, `audio`, `glb`, `other` |
| `status` | string | No | Filter by status: `pending`, `processing`, `completed`, `failed` |
### Request Example
```bash cURL theme={null}
curl -X GET "https://api.junis.ai/api/external/content?page=1&page_size=10&status=completed" \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.junis.ai/api/external/content",
headers={"X-API-Key": "jns_live_YOUR_API_KEY_HERE"},
params={
"page": 1,
"page_size": 10,
"status": "completed"
}
)
print(response.json())
```
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"content_type": "image",
"status": "completed",
"prompt": "A serene mountain landscape at sunset",
"replicate_version": "stability-ai/sdxl",
"output_url": "https://replicate.delivery/...",
"storage_url": "https://.blob.core.windows.net//generation/...",
"error_message": null,
"processing_time_ms": 12500,
"credit_cost": 0.05,
"created_at": "2026-01-01T10:30:00Z",
"completed_at": "2026-01-01T10:30:12Z"
}
],
"total": 42,
"page": 1,
"page_size": 10,
"has_more": true
}
```
### Response Fields
| Field | Type | Description |
| - | - | - |
| `items` | array | List of content items |
| `total` | integer | Total number of items matching the filter |
| `page` | integer | Current page number |
| `page_size` | integer | Number of items per page |
| `has_more` | boolean | Whether more pages are available |
### Content Item Fields
| Field | Type | Description |
| - | - | - |
| `id` | string | Unique content ID (UUID) |
| `content_type` | string | Type of content (`image`, `video`, `audio`, `glb`, `other`). `null` until generation completes. |
| `status` | string | Current status |
| `prompt` | string | Generation prompt (may be truncated in list view) |
| `replicate_version` | string | Replicate model version used |
| `output_url` | string | Direct URL to generated content (Replicate CDN) |
| `storage_url` | string | Permanent URL (Junis object storage) |
| `error_message` | string | Error description if failed |
| `processing_time_ms` | integer | Generation time in milliseconds |
| `credit_cost` | number | Credit cost for this generation (6 decimal places) |
| `created_at` | string | Creation timestamp (ISO 8601) |
| `completed_at` | string | Completion timestamp (ISO 8601) |
***
## Get Content Detail
Retrieve detailed information about a specific content item.
### Endpoint
```
GET https://api.junis.ai/api/external/content/{content_id}
```
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | - | - |
| `content_id` | string | **Yes** | Content ID (UUID format) |
### Request Example
```bash cURL theme={null}
curl -X GET "https://api.junis.ai/api/external/content/550e8400-e29b-41d4-a716-446655440000" \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE"
```
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"organization_id": "org-abc123",
"user_id": "user-xyz789",
"content_type": "image",
"status": "completed",
"prompt": "A serene mountain landscape at sunset with dramatic clouds and golden light reflecting off a calm lake in the foreground",
"replicate_version": "stability-ai/sdxl",
"replicate_prediction_id": "xyz123abc456",
"replicate_input": {
"prompt": "A serene mountain landscape at sunset...",
"negative_prompt": "blurry, low quality",
"width": 1024,
"height": 1024,
"num_inference_steps": 50
},
"output_url": "https://replicate.delivery/...",
"storage_path": "generation/image/550e8400_20260101_103000.png",
"storage_url": "https://.blob.core.windows.net//generation/...",
"error_message": null,
"retry_count": 0,
"max_retries": 3,
"credit_cost": 0.05,
"processing_time_ms": 12500,
"created_at": "2026-01-01T10:30:00Z",
"started_at": "2026-01-01T10:30:01Z",
"completed_at": "2026-01-01T10:30:12Z"
}
```
### Additional Detail Fields
| Field | Type | Description |
| - | - | - |
| `organization_id` | string | Organization that owns this content |
| `user_id` | string | User who initiated the generation |
| `replicate_prediction_id` | string | Replicate's internal prediction ID |
| `replicate_input` | object | Full input parameters sent to Replicate |
| `storage_path` | string | Path in cloud storage bucket |
| `retry_count` | integer | Number of retry attempts made |
| `max_retries` | integer | Maximum allowed retries (default: 3) |
| `started_at` | string | Processing start timestamp |
***
## Retry Failed Content
Retry a failed content generation. This re-queues the generation with the original parameters.
### Endpoint
```
POST https://api.junis.ai/api/external/content/{content_id}/retry
```
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | - | - |
| `content_id` | string | **Yes** | Content ID of failed generation (UUID format) |
### Prerequisites
* Content must have status `failed`
* `retry_count` must be less than `max_retries` (default: 3)
### Request Example
```bash cURL theme={null}
curl -X POST "https://api.junis.ai/api/external/content/550e8400-e29b-41d4-a716-446655440000/retry" \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE"
```
```python Python theme={null}
import requests
response = requests.post(
"https://api.junis.ai/api/external/content/550e8400-e29b-41d4-a716-446655440000/retry",
headers={"X-API-Key": "jns_live_YOUR_API_KEY_HERE"}
)
print(response.json())
```
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"success": true,
"message": "Content generation retry queued (attempt 1/3)",
"generation_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
### Response Fields
| Field | Type | Description |
| - | - | - |
| `success` | boolean | Whether retry was queued successfully |
| `message` | string | Human-readable status message |
| `generation_id` | string | Content ID being retried |
***
## Error Responses
### Common Errors
```json 400 Bad Request - Invalid Filter theme={null}
{
"detail": "Invalid content_type 'unknown'. Must be one of: ['image', 'video', 'audio', 'glb', 'other']"
}
```
```json 400 Bad Request - Cannot Retry theme={null}
{
"detail": "Cannot retry content with status 'completed'. Only failed content can be retried."
}
```
```json 400 Bad Request - Max Retries Reached theme={null}
{
"detail": "Maximum retry attempts (3) reached for this content."
}
```
```json 403 Forbidden - Insufficient Scopes theme={null}
{
"error": "insufficient_scopes",
"message": "API key does not have required permissions",
"required_scopes": ["content:read"],
"missing_scopes": ["content:read"],
"available_scopes": ["orchestrator:invoke", "sessions:read"]
}
```
```json 404 Not Found theme={null}
{
"detail": "Content not found: 550e8400-e29b-41d4-a716-446655440000"
}
```
***
## Best Practices
When content is in `pending` or `processing` status, poll the detail endpoint to check for completion:
```python theme={null}
import time
import requests
def wait_for_content(content_id, api_key, timeout=300, interval=5):
"""Poll until content is completed or failed."""
start = time.time()
while time.time() - start < timeout:
response = requests.get(
f"https://api.junis.ai/api/external/content/{content_id}",
headers={"X-API-Key": api_key}
)
data = response.json()
if data["status"] == "completed":
return data
elif data["status"] == "failed":
raise Exception(f"Generation failed: {data.get('error_message')}")
time.sleep(interval)
raise TimeoutError("Content generation timed out")
```
Use content type filters to retrieve specific types of generated content:
```bash theme={null}
# Get only images
curl "https://api.junis.ai/api/external/content?content_type=image"
# Get only videos
curl "https://api.junis.ai/api/external/content?content_type=video"
# Get failed content for monitoring
curl "https://api.junis.ai/api/external/content?status=failed"
```
Generated content has two URLs:
* **`output_url`**: Replicate's CDN URL (may expire)
* **`storage_url`**: Permanent Junis object storage URL
Always prefer `storage_url` for persistent access to content.
The `credit_cost` field shows the credit deducted for each generation. Use this for:
* Billing reconciliation
* Usage monitoring
* Cost optimization
Note: Credit costs support 6 decimal places for precision.
***
## Related Resources
Learn about API key scopes and authentication
Generate content through AI conversations
Invoke specific agents directly
Full list of error codes and solutions
# Error Codes
Source: https://docs.junis.ai/api-reference/error-codes
Comprehensive guide to Junis External API error codes and responses
## Overview
The Junis External API uses conventional HTTP status codes to indicate the success or failure of a request. Error responses include a structured JSON body with details about the error, making it easy to debug and handle errors programmatically.
**Base URL**: `https://api.junis.ai`
***
## Error Response Format
### Standard Error Response
All error responses follow this consistent structure:
```json theme={null}
{
"detail": "Human-readable error message"
}
```
### Structured Error Response
For more complex errors (like rate limiting or scope validation), the API returns additional context:
```json theme={null}
{
"error": "error_code",
"message": "Human-readable error message",
"code": "SPECIFIC_ERROR_CODE",
"details": {
// Additional error-specific details
}
}
```
***
## HTTP Status Codes
| Status Code | Name | Description |
| - | - | - |
| **200** | OK | Request succeeded. |
| **201** | Created | Resource successfully created. |
| **202** | Accepted | Request accepted for processing (async operations). |
| **204** | No Content | Request succeeded with no response body. |
| Status Code | Name | Description |
| - | - | - |
| **400** | Bad Request | Invalid request parameters or malformed JSON. |
| **401** | Unauthorized | Missing or invalid API key. |
| **403** | Forbidden | Authenticated but insufficient permissions (scopes). |
| **404** | Not Found | Resource does not exist. |
| **413** | Payload Too Large | Request body exceeds size limit. |
| **429** | Too Many Requests | Rate limit exceeded. |
| Status Code | Name | Description |
| - | - | - |
| **500** | Internal Server Error | Unexpected server error. |
| **502** | Bad Gateway | Upstream service unavailable. |
| **503** | Service Unavailable | Server temporarily overloaded or down. |
| **504** | Gateway Timeout | Upstream service timeout. |
***
## Common Error Codes
### Authentication Errors (401)
**Description**: No API key provided in request headers.
**Causes**:
* Missing `X-API-Key` header
* Empty `X-API-Key` header value
**Response Example**:
```json theme={null}
{
"detail": "API key required. Include X-API-Key header with your request."
}
```
**Response Headers**:
```
HTTP/1.1 401 Unauthorized
WWW-Authenticate: ApiKey
```
**Solution**:
```bash theme={null}
# Include X-API-Key header
curl https://api.junis.ai/api/external/v1/chat/completions \
-H "X-API-Key: jns_live_YOUR_API_KEY"
```
**Description**: API key not found, revoked, or expired.
**Causes**:
* API key deleted by organization admin
* API key revoked
* Wrong API key format
* API key expired (if expiration was set)
**Response Example**:
```json theme={null}
{
"detail": "Invalid or expired API key"
}
```
**Solution**:
* Verify API key in **Admin → API Keys**
* Generate new API key if revoked or expired
* Check API key format: `jns_live_` prefix + 32 characters
***
### Authorization Errors (403)
**Description**: API key lacks required scopes for the operation.
**Causes**:
* API key created without required scopes
* API key missing `orchestrator:invoke` scope (required for chat completions)
* API key missing `sessions:read` or `sessions:messages` scope
**Response Example**:
```json theme={null}
{
"error": "insufficient_scopes",
"message": "API key does not have required permissions",
"required_scopes": ["orchestrator:invoke"],
"missing_scopes": ["orchestrator:invoke"],
"available_scopes": ["sessions:read", "sessions:messages"]
}
```
**Solution**:
* Check API key scopes in **Admin → API Keys**
* Create new API key with required scopes:
* `orchestrator:invoke` - Chat completions (both streaming and non-streaming)
* `sessions:read` - List sessions and get session status
* `sessions:messages` - Get session messages
**Description**: API key belongs to a different organization.
**Causes**:
* Trying to access sessions created by another organization
* Trying to access resources not owned by your organization
**Response Example**:
```json theme={null}
{
"detail": "Access denied. Resource belongs to a different organization."
}
```
**Solution**:
* Verify you're using the correct API key for your organization
* Each API key can only access its own organization's resources
***
### Validation Errors (400)
**Description**: Request body validation failed.
**Causes**:
* Missing required fields
* Invalid field types
* Failed Pydantic validation
**Response Example**:
```json theme={null}
{
"detail": [
{
"loc": ["body", "messages"],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": ["body", "model"],
"msg": "str type expected",
"type": "type_error.str"
}
]
}
```
**Solution**:
* Check API documentation for required fields
* Verify data types match schema
* Example valid request:
```json theme={null}
{
"messages": [
{"role": "user", "content": "Hello"}
]
}
```
**Description**: Malformed JSON in request body.
**Causes**:
* Syntax error in JSON
* Unescaped special characters
* Trailing commas
**Response Example**:
```json theme={null}
{
"detail": "Expecting property name enclosed in double quotes"
}
```
**Solution**:
* Validate JSON syntax using a linter
* Escape special characters properly
* Remove trailing commas
***
### Resource Errors (404)
**Description**: Chat session does not exist.
**Causes**:
* Session deleted
* Wrong session\_id
* Session belongs to different organization
**Response Example**:
```json theme={null}
{
"detail": "Session not found"
}
```
**Solution**:
* Verify session\_id is correct
* Create new session via Chat Completions API
* List available sessions: `GET /api/external/sessions`
**Description**: Message does not exist in session.
**Causes**:
* Invalid message index
* Session has no messages yet
**Response Example**:
```json theme={null}
{
"detail": "Message not found in session"
}
```
**Solution**:
* Check session messages: `GET /api/external/sessions/{id}/messages`
* Verify message index is within range
***
### Rate Limit Errors (429)
**Description**: Too many requests in time window.
**Response Example**:
```json theme={null}
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded: 100 requests per minute",
"limit": 100,
"window": "minute",
"reset_at": 1767261600,
"retry_after": 45
}
```
**Response Headers**:
```
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit-Minute: 100
X-RateLimit-Remaining-Minute: 0
X-RateLimit-Reset-Minute: 1767261600
X-RateLimit-Limit-Hour: 3000
X-RateLimit-Remaining-Hour: 2950
X-RateLimit-Reset-Hour: 1767261600
Retry-After: 45
```
**Solution**:
* Wait for `retry_after` seconds before retrying
* Implement exponential backoff
* Monitor `X-RateLimit-Remaining-*` headers
* Request higher rate limits in **Admin → API Keys**
* Cache responses to reduce request frequency
See [Rate Limits](/api-reference/rate-limits) for more details.
***
### Payload Errors (413)
**Description**: Request body exceeds size limit.
**Causes**:
* Large message content
* Too many messages in array
* Maximum payload: 10MB
**Response Example**:
```json theme={null}
{
"detail": "Request payload too large. Maximum size: 10MB"
}
```
**Solution**:
* Split large requests into smaller chunks
* Summarize long messages
* Use pagination for message history
***
### Server Errors (500)
**Description**: Unexpected server error occurred.
**Causes**:
* Database connection failure
* External service timeout
* Unhandled exception
**Response Example**:
```json theme={null}
{
"detail": "Internal server error"
}
```
**Solution**:
* Retry request after a few seconds with exponential backoff
* Check [Status Page](https://status.junis.ai) for incidents
* Contact support if error persists: [contact@junis.ai](mailto:contact@junis.ai)
**Description**: LLM provider (Anthropic, OpenAI) unavailable.
**Causes**:
* Anthropic API outage
* OpenAI API rate limits
* Network timeout to LLM provider
**Response Example**:
```json theme={null}
{
"detail": "LLM service temporarily unavailable. Please try again."
}
```
**Solution**:
* Retry request with exponential backoff
* Check [Anthropic Status](https://status.anthropic.com)
* Wait a few minutes and retry
**Description**: Agent orchestration failed.
**Causes**:
* Agent not found
* Agent configuration error
* Tool execution failure
**Response Example**:
```json theme={null}
{
"detail": "Orchestrator processing error. Please try again."
}
```
**Solution**:
* Retry request
* Simplify prompt if issue persists
* Contact support with session\_id for debugging
***
## Error Handling Best Practices
### Retry Logic
Implement exponential backoff for transient errors (429, 500+ status codes):
* **429 Rate Limit**: Use the `Retry-After` header value to wait before retrying
* **5xx Server Errors**: Use exponential backoff (1s, 2s, 4s, etc.)
* **Max Retries**: Limit to 3-5 attempts to avoid infinite loops
* **Client Errors (4xx)**: Do not retry, fix the request instead
### Error Logging
Log errors with sufficient context for debugging:
* **Include**: Timestamp, status code, error message, request URL, method
* **Exclude**: Full API key (log only prefix like `jns_live_abc...`)
* **Structured Logging**: Use JSON format for easier parsing
### User-Friendly Error Messages
Present errors to users in a helpful way:
* **401 Unauthorized**: "Invalid API key. Please check your credentials."
* **403 Forbidden**: "Permission denied. Check your API key scopes."
* **429 Rate Limit**: "Too many requests. Please wait seconds."
* **5xx Server Errors**: "Our servers are experiencing issues. Please try again."
* **Generic Fallback**: Use the `detail` field from the error response
***
## Debugging Tools
### Check API Status
```bash theme={null}
curl https://api.junis.ai/api/health
```
**Healthy Response**:
```json theme={null}
{
"status": "healthy",
"version": "1.0.0",
"timestamp": "2026-01-01T10:00:00Z"
}
```
### Test API Key Authentication
```bash theme={null}
curl https://api.junis.ai/api/external/sessions \
-H "X-API-Key: jns_live_YOUR_API_KEY"
```
**Success Response** (200 OK):
```json theme={null}
{
"sessions": [],
"total": 0,
"limit": 50,
"offset": 0
}
```
**Error Response** (401 Unauthorized):
```json theme={null}
{
"detail": "Invalid or expired API key"
}
```
### Inspect Rate Limits
```bash theme={null}
curl -I https://api.junis.ai/api/external/v1/chat/completions \
-X POST \
-H "X-API-Key: jns_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"test"}]}'
```
**Response Headers**:
```
HTTP/1.1 200 OK
X-RateLimit-Limit-Minute: 100
X-RateLimit-Remaining-Minute: 55
X-RateLimit-Reset-Minute: 1767261600
X-RateLimit-Limit-Hour: 3000
X-RateLimit-Remaining-Hour: 2950
X-RateLimit-Reset-Hour: 1767261600
```
***
## Monitoring and Alerts
### Track Error Rates
Monitor these metrics for your API integration:
Track 2xx responses vs total requests
**Target**: > 99% success rate
Client errors (auth, validation, rate limits)
**Alert**: > 5% of requests
Server errors (outages, timeouts)
**Alert**: Any occurrence
P50, P95, P99 latency
**Target**: P95 \< 2 seconds
### Sample Monitoring Code
```python theme={null}
import prometheus_client as prom
# Define metrics
api_requests = prom.Counter('api_requests_total', 'Total API requests', ['status'])
api_latency = prom.Histogram('api_latency_seconds', 'API request latency')
# Track request
with api_latency.time():
response = requests.post(url, headers=headers, json=payload)
api_requests.labels(status=response.status_code).inc()
```
***
## Need Help?
Check real-time API status and incidents
Learn about rate limiting and optimization
Review API key authentication
Contact support for assistance
# External API Overview
Source: https://docs.junis.ai/api-reference/external-overview
Integrate Junis AI agents into your applications
## Introduction
The External API allows you to integrate Junis AI agents into your own applications. Use your API key to invoke agents, manage sessions, and build powerful AI-powered features.
External API requires an active subscription (Basic or Pro). Credits are consumed for each API call.
***
## Base URL
```
https://api.junis.ai/api/external
```
For OpenAI-compatible endpoints:
```
https://api.junis.ai/api/external/v1
```
***
## Authentication
All External API requests require an API key in the `X-API-Key` header:
```bash theme={null}
curl -H "X-API-Key: jns_live_xxxxxxxxxxxxxxxx" \
https://api.junis.ai/api/external/v1/chat/completions
```
Never expose your API key in client-side code. Use server-side calls or a backend proxy.
***
## API Key Scopes
API keys can be created with specific scopes to limit access:
| Scope | Description |
| - | - |
| `orchestrator:invoke` | Invoke the main orchestrator via chat completions |
| `orchestrator:stream` | Required in addition to `orchestrator:invoke` when `stream: true` |
| `agents:list` | List available agents in organization |
| `agents:invoke` | Invoke individual agents directly |
| `sessions:read` | Read session history and messages |
| `rag:upload` | Upload files to RAG Datastore |
| `rag:read` | Read RAG uploads and DataStores |
***
## Available Endpoints
### OpenAI-Compatible
`POST /v1/chat/completions` - OpenAI-compatible chat API
`POST /v1/warmup` - Build the orchestrator (and optionally one agent) before the first message — no LLM call
### Agent Management
`GET /agents` - List available agents
`POST /agents/{agent_id}/completions` - Invoke specific agent
### Session Management
`GET /sessions` - List chat sessions
`GET /sessions/{session_id}/messages` - Get session messages
### RAG Knowledge Base
`POST /rag/datastores` - Create new DataStore
`POST /rag/upload` - Upload files to DataStore
`GET /rag/uploads` - List upload history
`GET /rag/datastores` - List DataStores
***
## Quick Start
### 1. Get Your API Key
1. Go to **Admin > API Keys**
2. Click **Create API Key**
3. Select required scopes
4. Copy and securely store the key (shown only once)
### 2. Make Your First Call
```bash theme={null}
curl -X POST https://api.junis.ai/api/external/v1/chat/completions \
-H "X-API-Key: jns_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"model": "junis-orchestrator",
"messages": [{"role": "user", "content": "Hello, how are you?"}],
"stream": false
}'
```
### 3. Parse the Response
```json theme={null}
{
"id": "cmpl-abc123",
"object": "chat.completion",
"created": 1767261600,
"model": "junis-orchestrator",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! I'm doing well, thank you for asking..."
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 25,
"total_tokens": 35
}
}
```
***
## SDK Support
### OpenAI Python SDK
```python theme={null}
import openai
client = openai.OpenAI(
api_key="jns_live_your_api_key",
base_url="https://api.junis.ai/api/external/v1"
)
response = client.chat.completions.create(
model="junis-orchestrator",
messages=[{"role": "user", "content": "Hello!"}]
)
print(response.choices[0].message.content)
```
### OpenAI Node.js SDK
```javascript theme={null}
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: 'jns_live_your_api_key',
baseURL: 'https://api.junis.ai/api/external/v1'
});
const response = await openai.chat.completions.create({
model: 'junis-orchestrator',
messages: [{ role: 'user', content: 'Hello!' }]
});
console.log(response.choices[0].message.content);
```
***
## Streaming
Enable streaming for real-time responses:
```python theme={null}
stream = client.chat.completions.create(
model="junis-orchestrator",
messages=[{"role": "user", "content": "Tell me a story"}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
```
***
## Rate Limits
| Limit | Default |
| - | - |
| Requests per minute, per key | 100 |
| Requests per hour, per key | 3,000 (organization key) · 2,000 (personal key) |
| Requests per minute, whole organization (all keys combined) | 300 |
Rate limit headers are included in all responses: `X-RateLimit-Limit-Minute`, `X-RateLimit-Remaining-Minute`, `X-RateLimit-Reset-Minute`, their `-Hour` equivalents, and `X-RateLimit-Limit-Org-Minute` / `X-RateLimit-Remaining-Org-Minute`. See [Rate Limits](/api-reference/rate-limits).
***
## Error Handling
All errors follow a consistent format:
```json theme={null}
{
"error": {
"message": "Invalid API key",
"type": "authentication_error",
"code": "invalid_api_key"
}
}
```
### Common Error Codes
| Code | Status | Description |
| - | - | - |
| `invalid_api_key` | 401 | API key is invalid or revoked |
| `insufficient_credits` | 402 | Not enough credits for request |
| `rate_limit_exceeded` | 429 | Too many requests |
| `scope_not_allowed` | 403 | API key lacks required scope |
| `agent_not_found` | 404 | Specified agent doesn't exist |
***
## Related
OpenAI-compatible chat API
Individual agent invocation
# RAG Knowledge Base
Source: https://docs.junis.ai/api-reference/rag
Upload and manage documents for RAG-powered search
## Overview
The RAG (Retrieval-Augmented Generation) API allows you to upload documents and create knowledge bases that your AI agents can search. Documents are uploaded to Google Cloud Storage and indexed in Vertex AI Search for semantic retrieval.
**Workflow**: First create a DataStore using `POST /rag/datastores`, then upload files to that DataStore using `POST /rag/upload`. Files are indexed asynchronously via background tasks.
***
## Authentication
All RAG endpoints require API key authentication with specific scopes:
| Endpoint | Required Scope |
| - | - |
| `POST /rag/datastores` | `rag:upload` |
| `GET /rag/datastores` | `rag:read` |
| `GET /rag/datastores/{id}/files` | `rag:read` |
| `DELETE /rag/datastores/{id}` | `rag:delete` |
| `DELETE /rag/datastores/{id}/files/{filename}` | `rag:delete` |
| `POST /rag/upload` | `rag:upload` |
| `GET /rag/uploads` | `rag:read` |
| `GET /rag/uploads/{upload_id}` | `rag:read` |
**Scopes Required**: The `rag:upload`, `rag:read`, and `rag:delete` scopes must be explicitly enabled when creating API keys. They are not enabled by default.
Include your API key in the `X-API-Key` header:
```bash theme={null}
X-API-Key: jns_live_YOUR_API_KEY_HERE
```
See [Authentication](/api-reference/authentication) for details.
***
## Supported File Types
The following file types can be uploaded to RAG Datastores:
| MIME Type | Extension | Description |
| - | - | - |
| `application/pdf` | `.pdf` | PDF documents |
| `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | `.docx` | Microsoft Word documents |
| `application/vnd.openxmlformats-officedocument.presentationml.presentation` | `.pptx` | Microsoft PowerPoint presentations |
| `text/plain` | `.txt` | Plain text files |
| `text/html` | `.html` | HTML documents |
| `application/json` | `.json` | JSON files |
| `application/x-ndjson` | `.jsonl` | Newline-delimited JSON |
| `text/markdown` | `.md`, `.markdown` | Markdown documents |
**File Limits**: Maximum 10MB per file, maximum 10 files per upload request.
***
## Create DataStore
Create a new DataStore to organize uploaded documents.
### Endpoint
```
POST https://api.junis.ai/api/external/rag/datastores
```
### Request Body
| Field | Type | Required | Description |
| - | - | - | - |
| `display_name` | string | **Yes** | Human-readable name for the DataStore (e.g., "Company Policies") |
| `description` | string | No | Optional description of the DataStore's purpose |
### Request Example
```bash cURL theme={null}
curl -X POST "https://api.junis.ai/api/external/rag/datastores" \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Company Policies",
"description": "HR policies and employee handbook"
}'
```
```python Python theme={null}
import requests
response = requests.post(
"https://api.junis.ai/api/external/rag/datastores",
headers={
"X-API-Key": "jns_live_YOUR_API_KEY_HERE",
"Content-Type": "application/json"
},
json={
"display_name": "Company Policies",
"description": "HR policies and employee handbook"
}
)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch('https://api.junis.ai/api/external/rag/datastores', {
method: 'POST',
headers: {
'X-API-Key': 'jns_live_YOUR_API_KEY_HERE',
'Content-Type': 'application/json'
},
body: JSON.stringify({
display_name: 'Company Policies',
description: 'HR policies and employee handbook'
})
});
const data = await response.json();
console.log(data);
```
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"success": true,
"datastore_id": "company-policies-a1b2c3d4",
"display_name": "Company Policies",
"description": "HR policies and employee handbook",
"message": "DataStore 'Company Policies' created successfully"
}
```
### Response Fields
| Field | Type | Description |
| - | - | - |
| `success` | boolean | Whether creation was successful |
| `datastore_id` | string | Unique ID for the DataStore (use this for uploads) |
| `display_name` | string | Human-readable name |
| `description` | string | DataStore description (null if not provided) |
| `message` | string | Success message |
### Error Responses
```json 409 Conflict - DataStore Already Exists theme={null}
{
"detail": {
"error": {
"message": "DataStore with name 'Company Policies' already exists",
"type": "conflict",
"code": "datastore_exists",
"existing_datastore_id": "company-policies-a1b2c3d4"
}
}
}
```
***
## Upload Files
Upload files to an existing DataStore. Files are encoded as Base64 and indexed asynchronously.
### Endpoint
```
POST https://api.junis.ai/api/external/rag/upload
```
**DataStore Required**: You must create a DataStore first using `POST /rag/datastores`. File uploads to non-existent DataStores will fail with a 404 error.
### Request Body
| Field | Type | Required | Description |
| - | - | - | - |
| `datastore_id` | string | **Yes** | ID of existing DataStore (from create response) |
| `files` | array | **Yes** | Array of files to upload (min: 1, max: 10) |
| `files[].filename` | string | **Yes** | Filename with extension (e.g., "policy.pdf") |
| `files[].content` | string | **Yes** | Base64-encoded file content |
| `files[].content_type` | string | No | MIME type (auto-detected from extension if not provided) |
### Request Example
```bash cURL theme={null}
# First, encode your file as Base64
BASE64_CONTENT=$(base64 -i policy.pdf)
curl -X POST "https://api.junis.ai/api/external/rag/upload" \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"datastore_id": "company-policies-a1b2c3d4",
"files": [
{
"filename": "policy.pdf",
"content": "'"${BASE64_CONTENT}"'"
}
]
}'
```
```python Python theme={null}
import base64
import requests
# Read and encode file
with open("policy.pdf", "rb") as f:
content = base64.b64encode(f.read()).decode()
response = requests.post(
"https://api.junis.ai/api/external/rag/upload",
headers={
"X-API-Key": "jns_live_YOUR_API_KEY_HERE",
"Content-Type": "application/json"
},
json={
"datastore_id": "company-policies-a1b2c3d4",
"files": [
{
"filename": "policy.pdf",
"content": content
}
]
}
)
print(response.json())
```
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"success": true,
"datastore_id": "company-policies-a1b2c3d4",
"datastore_name": "Company Policies",
"uploads": [
{
"upload_id": "550e8400-e29b-41d4-a716-446655440000",
"filename": "policy.pdf",
"status": "processing"
}
],
"message": "1 file(s) upload started"
}
```
### Response Fields
| Field | Type | Description |
| - | - | - |
| `success` | boolean | Whether upload was initiated |
| `datastore_id` | string | Target DataStore ID |
| `datastore_name` | string | Target DataStore display name |
| `uploads` | array | Status of each uploaded file |
| `uploads[].upload_id` | string | Unique upload ID (UUID) |
| `uploads[].filename` | string | Original filename |
| `uploads[].status` | string | Upload status (`processing`) |
| `message` | string | Summary message |
### Error Responses
```json 404 Not Found - DataStore Not Found theme={null}
{
"detail": {
"error": {
"message": "DataStore not found: invalid-datastore-id. Create it first using POST /rag/datastores",
"type": "not_found",
"code": "datastore_not_found"
}
}
}
```
```json 400 Bad Request - Unsupported File Type theme={null}
{
"detail": {
"error": {
"message": "Unsupported file extension: .exe",
"type": "validation_error",
"code": "unsupported_file_type"
}
}
}
```
```json 400 Bad Request - File Too Large theme={null}
{
"detail": {
"error": {
"message": "File size exceeds 10MB limit",
"type": "validation_error",
"code": "file_too_large"
}
}
}
```
```json 400 Bad Request - Invalid Base64 theme={null}
{
"detail": {
"error": {
"message": "Base64 decoding failed: Invalid padding",
"type": "validation_error",
"code": "invalid_base64"
}
}
}
```
***
## List DataStores
Retrieve all DataStores in your organization.
### Endpoint
```
GET https://api.junis.ai/api/external/rag/datastores
```
### Request Example
```bash cURL theme={null}
curl -X GET "https://api.junis.ai/api/external/rag/datastores" \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.junis.ai/api/external/rag/datastores",
headers={"X-API-Key": "jns_live_YOUR_API_KEY_HERE"}
)
print(response.json())
```
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"datastores": [
{
"datastore_id": "company-policies-a1b2c3d4",
"display_name": "Company Policies",
"description": "HR policies and employee handbook",
"document_count": 5,
"created_at": "2026-01-01T10:00:00Z",
"is_active": true
},
{
"datastore_id": "product-docs-e5f6g7h8",
"display_name": "Product Documentation",
"description": null,
"document_count": 12,
"created_at": "2026-01-01T08:30:00Z",
"is_active": true
}
],
"total": 2
}
```
### Response Fields
| Field | Type | Description |
| - | - | - |
| `datastores` | array | List of DataStores |
| `datastores[].datastore_id` | string | Unique DataStore ID |
| `datastores[].display_name` | string | Human-readable name |
| `datastores[].description` | string | DataStore description (null if not set) |
| `datastores[].document_count` | integer | Number of completed uploads |
| `datastores[].created_at` | string | Creation timestamp (ISO 8601) |
| `datastores[].is_active` | boolean | Whether DataStore is active |
| `total` | integer | Total number of DataStores |
***
## List Uploads
Retrieve a paginated list of file uploads for your organization.
### Endpoint
```
GET https://api.junis.ai/api/external/rag/uploads
```
### Query Parameters
| Parameter | Type | Required | Description |
| - | - | - | - |
| `limit` | integer | No | Maximum items to return (default: `50`, max: `100`) |
| `offset` | integer | No | Number of items to skip (default: `0`) |
| `status` | string | No | Filter by status: `pending`, `processing`, `indexing`, `completed`, `failed` |
| `datastore_id` | string | No | Filter by DataStore ID |
### Request Example
```bash cURL theme={null}
curl -X GET "https://api.junis.ai/api/external/rag/uploads?limit=10&status=completed" \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.junis.ai/api/external/rag/uploads",
headers={"X-API-Key": "jns_live_YOUR_API_KEY_HERE"},
params={
"limit": 10,
"status": "completed"
}
)
print(response.json())
```
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"uploads": [
{
"upload_id": "550e8400-e29b-41d4-a716-446655440000",
"filename": "policy.pdf",
"file_size": 102400,
"file_type": "application/pdf",
"datastore_id": "company-policies-a1b2c3d4",
"datastore_name": "Company Policies",
"status": "completed",
"uploaded_at": "2026-01-01T10:00:00Z",
"indexed_at": "2026-01-01T10:05:00Z",
"error_message": null
}
],
"total": 42,
"limit": 10,
"offset": 0
}
```
### Upload Status Values
| Status | Description |
| - | - |
| `pending` | Upload received, waiting for processing |
| `processing` | File being uploaded to GCS |
| `indexing` | File being indexed in Vertex AI Search |
| `completed` | Indexing complete, document is searchable |
| `failed` | Upload or indexing failed |
### Response Fields
| Field | Type | Description |
| - | - | - |
| `uploads` | array | List of uploads |
| `uploads[].upload_id` | string | Unique upload ID (UUID) |
| `uploads[].filename` | string | Original filename |
| `uploads[].file_size` | integer | File size in bytes |
| `uploads[].file_type` | string | MIME type |
| `uploads[].datastore_id` | string | Target DataStore ID |
| `uploads[].datastore_name` | string | Target DataStore name |
| `uploads[].status` | string | Current status |
| `uploads[].uploaded_at` | string | Upload timestamp (ISO 8601) |
| `uploads[].indexed_at` | string | Indexing completion timestamp (null if not indexed) |
| `uploads[].error_message` | string | Error description if failed |
| `total` | integer | Total matching uploads |
| `limit` | integer | Items per page |
| `offset` | integer | Current offset |
***
## Get Upload Status
Retrieve detailed status of a specific upload.
### Endpoint
```
GET https://api.junis.ai/api/external/rag/uploads/{upload_id}
```
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | - | - |
| `upload_id` | string | **Yes** | Upload ID (UUID format) |
### Request Example
```bash cURL theme={null}
curl -X GET "https://api.junis.ai/api/external/rag/uploads/550e8400-e29b-41d4-a716-446655440000" \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE"
```
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"upload_id": "550e8400-e29b-41d4-a716-446655440000",
"filename": "policy.pdf",
"file_size": 102400,
"file_type": "application/pdf",
"status": "completed",
"ready": true,
"datastore_id": "company-policies-a1b2c3d4",
"datastore_name": "Company Policies",
"uploaded_at": "2026-01-01T10:00:00Z",
"indexed_at": "2026-01-01T10:05:00Z",
"error_message": null
}
```
### Response Fields
| Field | Type | Description |
| - | - | - |
| `upload_id` | string | Unique upload ID (UUID) |
| `filename` | string | Original filename |
| `file_size` | integer | File size in bytes |
| `file_type` | string | MIME type |
| `status` | string | Current status |
| `ready` | boolean | `true` if document is searchable (`status === completed`) |
| `datastore_id` | string | Target DataStore ID |
| `datastore_name` | string | Target DataStore name |
| `uploaded_at` | string | Upload timestamp (ISO 8601) |
| `indexed_at` | string | Indexing completion timestamp (null if not indexed) |
| `error_message` | string | Error description if failed |
### Error Responses
```json 404 Not Found theme={null}
{
"detail": {
"error": {
"message": "Upload not found: 550e8400-e29b-41d4-a716-446655440000",
"type": "not_found",
"code": "upload_not_found"
}
}
}
```
***
## List DataStore Files
Retrieve all files in a specific DataStore with their indexing status.
### Endpoint
```
GET https://api.junis.ai/api/external/rag/datastores/{datastore_id}/files
```
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | - | - |
| `datastore_id` | string | **Yes** | DataStore ID |
### Request Example
```bash cURL theme={null}
curl -X GET "https://api.junis.ai/api/external/rag/datastores/company-policies-a1b2c3d4/files" \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE"
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.junis.ai/api/external/rag/datastores/company-policies-a1b2c3d4/files",
headers={"X-API-Key": "jns_live_YOUR_API_KEY_HERE"}
)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.junis.ai/api/external/rag/datastores/company-policies-a1b2c3d4/files',
{
headers: { 'X-API-Key': 'jns_live_YOUR_API_KEY_HERE' }
}
);
const data = await response.json();
console.log(data);
```
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"datastore_id": "company-policies-a1b2c3d4",
"datastore_name": "Company Policies",
"files": [
{
"name": "employee-handbook.pdf",
"size": 2048576,
"content_type": "application/pdf",
"uploaded_at": "2026-01-01T10:00:00Z",
"status": "completed"
},
{
"name": "vacation-policy.docx",
"size": 102400,
"content_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"uploaded_at": "2026-01-01T10:05:00Z",
"status": "indexing"
}
],
"total_files": 2,
"indexed_files": 1
}
```
### Response Fields
| Field | Type | Description |
| - | - | - |
| `datastore_id` | string | DataStore ID |
| `datastore_name` | string | DataStore display name |
| `files` | array | List of files in the DataStore |
| `files[].name` | string | Filename |
| `files[].size` | integer | File size in bytes |
| `files[].content_type` | string | MIME type |
| `files[].uploaded_at` | string | Upload timestamp (ISO 8601) |
| `files[].status` | string | Indexing status (`pending`, `indexing`, `completed`, `failed`) |
| `total_files` | integer | Total number of files |
| `indexed_files` | integer | Number of fully indexed files |
### Error Responses
```json 404 Not Found theme={null}
{
"detail": {
"error": {
"message": "DataStore not found: invalid-datastore-id",
"type": "not_found",
"code": "datastore_not_found"
}
}
}
```
***
## Delete DataStore
Delete a DataStore and all its contents (files and indexed documents).
### Endpoint
```
DELETE https://api.junis.ai/api/external/rag/datastores/{datastore_id}
```
**Destructive Operation**: This permanently deletes all files and indexed documents in the DataStore. This action cannot be undone.
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | - | - |
| `datastore_id` | string | **Yes** | DataStore ID to delete |
### Query Parameters
| Parameter | Type | Required | Description |
| - | - | - | - |
| `force` | boolean | No | If `true`, delete even if DataStore is connected to agents (default: `false`) |
### Request Example
```bash cURL theme={null}
curl -X DELETE "https://api.junis.ai/api/external/rag/datastores/company-policies-a1b2c3d4?force=true" \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE"
```
```python Python theme={null}
import requests
response = requests.delete(
"https://api.junis.ai/api/external/rag/datastores/company-policies-a1b2c3d4",
headers={"X-API-Key": "jns_live_YOUR_API_KEY_HERE"},
params={"force": True}
)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.junis.ai/api/external/rag/datastores/company-policies-a1b2c3d4?force=true',
{
method: 'DELETE',
headers: { 'X-API-Key': 'jns_live_YOUR_API_KEY_HERE' }
}
);
const data = await response.json();
console.log(data);
```
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"success": true,
"datastore_id": "company-policies-a1b2c3d4",
"message": "DataStore 'Company Policies' deleted successfully"
}
```
### Response Fields
| Field | Type | Description |
| - | - | - |
| `success` | boolean | Whether deletion was successful |
| `datastore_id` | string | Deleted DataStore ID |
| `message` | string | Success message |
### Error Responses
```json 404 Not Found theme={null}
{
"detail": {
"error": {
"message": "DataStore not found: invalid-datastore-id",
"type": "not_found",
"code": "datastore_not_found"
}
}
}
```
```json 409 Conflict - DataStore In Use theme={null}
{
"detail": {
"error": {
"message": "DataStore is connected to agents. Use force=true to delete anyway.",
"type": "conflict",
"code": "datastore_in_use",
"connected_agents": ["brand-analyzer", "content-writer"]
}
}
}
```
***
## Delete File
Delete a single file from a DataStore while preserving the DataStore itself.
### Endpoint
```
DELETE https://api.junis.ai/api/external/rag/datastores/{datastore_id}/files/{filename}
```
**Partial Deletion**: Only the specified file is removed. The DataStore and other files remain intact.
### Path Parameters
| Parameter | Type | Required | Description |
| - | - | - | - |
| `datastore_id` | string | **Yes** | DataStore ID |
| `filename` | string | **Yes** | Name of the file to delete (URL-encoded if contains special characters) |
### Request Example
```bash cURL theme={null}
curl -X DELETE "https://api.junis.ai/api/external/rag/datastores/company-policies-a1b2c3d4/files/old-policy.pdf" \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE"
```
```python Python theme={null}
import requests
response = requests.delete(
"https://api.junis.ai/api/external/rag/datastores/company-policies-a1b2c3d4/files/old-policy.pdf",
headers={"X-API-Key": "jns_live_YOUR_API_KEY_HERE"}
)
print(response.json())
```
```javascript JavaScript theme={null}
const response = await fetch(
'https://api.junis.ai/api/external/rag/datastores/company-policies-a1b2c3d4/files/old-policy.pdf',
{
method: 'DELETE',
headers: { 'X-API-Key': 'jns_live_YOUR_API_KEY_HERE' }
}
);
const data = await response.json();
console.log(data);
```
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"success": true,
"datastore_id": "company-policies-a1b2c3d4",
"filename": "old-policy.pdf",
"remaining_files": 4,
"message": "File 'old-policy.pdf' deleted from 'Company Policies'"
}
```
### Response Fields
| Field | Type | Description |
| - | - | - |
| `success` | boolean | Whether deletion was successful |
| `datastore_id` | string | DataStore ID |
| `filename` | string | Deleted filename |
| `remaining_files` | integer | Number of files remaining in the DataStore |
| `message` | string | Success message |
### Error Responses
```json 404 Not Found - DataStore theme={null}
{
"detail": {
"error": {
"message": "DataStore not found: invalid-datastore-id",
"type": "not_found",
"code": "datastore_not_found"
}
}
}
```
```json 500 Internal Server Error - File Not Found theme={null}
{
"detail": {
"error": {
"message": "Failed to delete file: File not found in DataStore",
"type": "deletion_error",
"code": "file_deletion_failed"
}
}
}
```
***
## Best Practices
When an upload is in `processing` or `indexing` status, poll the status endpoint to check for completion:
```python theme={null}
import time
import requests
def wait_for_upload(upload_id, api_key, timeout=300, interval=5):
"""Poll until upload is completed or failed."""
start = time.time()
while time.time() - start < timeout:
response = requests.get(
f"https://api.junis.ai/api/external/rag/uploads/{upload_id}",
headers={"X-API-Key": api_key}
)
data = response.json()
if data["ready"]:
return data
elif data["status"] == "failed":
raise Exception(f"Upload failed: {data.get('error_message')}")
time.sleep(interval)
raise TimeoutError("Upload indexing timed out")
```
Upload multiple related files in a single request (up to 10 files):
```python theme={null}
import base64
import requests
import os
def upload_directory(datastore_id, directory_path, api_key):
"""Upload all supported files from a directory."""
files = []
for filename in os.listdir(directory_path):
filepath = os.path.join(directory_path, filename)
if os.path.isfile(filepath):
with open(filepath, "rb") as f:
content = base64.b64encode(f.read()).decode()
files.append({
"filename": filename,
"content": content
})
response = requests.post(
"https://api.junis.ai/api/external/rag/upload",
headers={"X-API-Key": api_key, "Content-Type": "application/json"},
json={"datastore_id": datastore_id, "files": files[:10]} # Max 10
)
return response.json()
```
Create separate DataStores for different topics or document types:
* **By Department**: "HR Policies", "Engineering Docs", "Sales Materials"
* **By Project**: "Project Alpha", "Project Beta"
* **By Type**: "Legal Contracts", "Technical Specs", "Meeting Notes"
This helps agents retrieve more relevant information by searching specific DataStores.
For large documents:
* Split large PDFs into smaller chapters
* Remove unnecessary images from documents
* Use text formats (TXT, MD) when formatting isn't important
* Compress images before including in documents
***
## Related Resources
Learn about API key scopes and authentication
User guide for RAG features
Use RAG in conversations
Full list of error codes and solutions
# Rate Limits
Source: https://docs.junis.ai/api-reference/rate-limits
Understanding API key rate limiting and optimization strategies
## Overview
The Junis External API implements rate limiting to ensure fair usage and maintain service quality for all users. Rate limits are applied **per API key**, and all keys of an organization also share one **organization-wide** per-minute total. Per-key limits can be adjusted by an organization admin.
**Base URL**: `https://api.junis.ai`
Each API key has its own per-minute and per-hour counters. On top of that, all keys of an organization share one per-minute total (300 requests by default), so creating more keys does not raise the organization's total.
***
## Rate Limiting Architecture
The External API uses **Redis-based fixed-window counters** with three windows (per key per minute, per key per hour, per organization per minute) for distributed rate limiting across multiple server instances.
**Storage**: Redis with distributed support across all API servers
**Windows**: Three fixed windows aligned to the clock
* **Per key, per minute**: resets at the start of every minute
* **Per key, per hour**: resets at the start of every hour
* **Per organization, per minute**: all keys of the organization combined, resets at the start of every minute
**Algorithm**: Fixed-window counters with atomic increments (one Redis round trip per request)
**Check Before Request**: Rate limits are checked before processing your request
**Headers Included**: Every response includes current rate limit status
**429 on Exceed**: Returns `429 Too Many Requests` if limit exceeded
**Automatic Reset**: Counters automatically reset at the end of each window
***
## Default Rate Limits
When you create a new API key, the following default limits apply:
| Limit | Organization key (Admin → API Keys) | Personal key (Settings → API Keys) |
| - | - | - |
| **Requests per minute, per key** | 100 | 100 |
| **Requests per hour, per key** | 3,000 | 2,000 |
| **Requests per minute, whole organization** | 300 (all keys combined) | 300 (all keys combined) |
Per-key limits of an existing key can be raised or lowered by an organization admin through the admin API (`PUT /api/admin/api-keys/{key_id}` with `rate_limit_per_minute` 1–1,000 and/or `rate_limit_per_hour` 1–100,000). The change applies from the next request, with no redeploy. The organization-wide total is a platform setting; contact Junis if your integration needs more.
***
## Rate Limit Headers
Every API response includes headers that show your current rate limit status.
### Response Headers
```http theme={null}
X-RateLimit-Limit-Minute: 100
X-RateLimit-Remaining-Minute: 55
X-RateLimit-Reset-Minute: 1767261600
X-RateLimit-Limit-Hour: 3000
X-RateLimit-Remaining-Hour: 2950
X-RateLimit-Reset-Hour: 1767261600
X-RateLimit-Limit-Org-Minute: 300
X-RateLimit-Remaining-Org-Minute: 240
```
| Header | Description |
| - | - |
| `X-RateLimit-Limit-Minute` | Maximum requests allowed per minute for this key |
| `X-RateLimit-Remaining-Minute` | Requests remaining for this key in the current minute window |
| `X-RateLimit-Reset-Minute` | Unix timestamp when the minute window resets |
| `X-RateLimit-Limit-Hour` | Maximum requests allowed per hour for this key |
| `X-RateLimit-Remaining-Hour` | Requests remaining for this key in the current hour window |
| `X-RateLimit-Reset-Hour` | Unix timestamp when the hour window resets |
| `X-RateLimit-Limit-Org-Minute` | Maximum requests per minute for all keys of your organization combined |
| `X-RateLimit-Remaining-Org-Minute` | Requests remaining for the whole organization in the current minute window (resets with the minute window) |
Responses also carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Window` without a suffix. Those belong to a platform-level flood guard in front of the key limiter and are not your key's quota — read the suffixed headers above.
Always monitor `X-RateLimit-Remaining-*` headers to avoid hitting rate limits. Consider pausing requests when remaining count is low.
***
## 429 Too Many Requests
When you exceed your rate limit, the API returns a `429` status code.
### Response Format
**HTTP Status**:
```
HTTP/1.1 429 Too Many Requests
Retry-After: 45
```
**Headers**:
```
X-RateLimit-Limit-Minute: 100
X-RateLimit-Remaining-Minute: 0
X-RateLimit-Reset-Minute: 1767261600
X-RateLimit-Limit-Hour: 3000
X-RateLimit-Remaining-Hour: 2950
X-RateLimit-Reset-Hour: 1767261600
X-RateLimit-Limit-Org-Minute: 300
X-RateLimit-Remaining-Org-Minute: 180
Retry-After: 45
```
**Body**:
```json theme={null}
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded: 100 requests per minute",
"limit": 100,
"window": "minute",
"reset_at": 1767261600,
"retry_after": 45
}
```
| Field | Description |
| - | - |
| `error` | Error type: `rate_limit_exceeded` |
| `message` | Human-readable error message |
| `limit` | The rate limit that was exceeded |
| `window` | Which window was exceeded: `minute`, `hour` (this key) or `org_minute` (all keys of the organization) |
| `reset_at` | Unix timestamp when the window resets |
| `retry_after` | Seconds to wait before retrying |
When the organization-wide total is exceeded, `window` is `org_minute`, `limit` is the organization total and `message` reads `Organization rate limit exceeded: 300 requests per minute across all API keys`. The per-key windows are checked first.
***
## Handling Rate Limits
### Best Practices
* **Monitor Headers**: Always check `X-RateLimit-Remaining-Minute`, `X-RateLimit-Remaining-Hour` and `X-RateLimit-Remaining-Org-Minute` headers to track your usage
* **Implement Exponential Backoff**: When you receive a 429 response, use the `Retry-After` header value to wait before retrying
* **Cache Responses**: Implement caching (TTL: 5 minutes recommended) to reduce API calls for frequently accessed data
* **Batch Requests**: Use paginated endpoints instead of individual requests (e.g., `GET /api/external/sessions?limit=100`)
* **Use Streaming**: For real-time chat, use `stream: true` parameter instead of polling to avoid repeated API calls
***
## Rate Limit Optimization
### Identify Bottlenecks
1. **Log Rate Limit Headers**: Track `X-RateLimit-Remaining-*` headers in your application logs
2. **Analyze Request Patterns**: Identify high-frequency endpoints that may benefit from caching or batching
* Repeated session fetches → Add caching
* Individual message fetches → Use paginated list endpoints
* Polling for updates → Switch to streaming
3. **Implement Caching**: Use Redis or in-memory cache with 5-minute TTL for frequently accessed data
4. **Monitor & Alert**: Set up alerts when rate limit usage exceeds 90%
***
## Testing Rate Limits
### Local Testing
Test your rate limit handling before deploying to production:
```bash theme={null}
# Test the per-key minute limit (default 100 per minute — send 105 requests in 1 minute)
for i in {1..105}; do
curl -s -o /dev/null -w "%{http_code}\n" \
https://api.junis.ai/api/external/v1/chat/completions \
-H "X-API-Key: jns_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"test"}]}'
sleep 0.5
done
```
***
## Increasing Rate Limits
Per-key limits are stored on the key itself, so they can be changed without redeploying anything:
1. An organization admin calls `PUT /api/admin/api-keys/{key_id}` (JWT authentication) with `rate_limit_per_minute` (1–1,000) and/or `rate_limit_per_hour` (1–100,000).
2. The new values apply from the next request made with that key.
Only organization admins can modify API key rate limits. Raising a key above the organization-wide total (300 requests per minute across all keys by default) does not add capacity on its own — the organization total still applies. Contact Junis to raise the organization total.
Set rate limits based on your actual usage patterns. Start conservative and increase as needed.
***
## FAQs
**Answer**: Both. Each key has its own per-minute and per-hour limits, and all keys of the organization share one per-minute total (300 by default).
Example: If your organization has 2 API keys:
* API Key 1: 100 requests/minute
* API Key 2: 100 requests/minute
* Organization total: 300 requests/minute — the keys together can never exceed it, so adding a third key does not add capacity beyond the total
**Response**: `429 Too Many Requests` with `Retry-After` header
**Action**: Wait for the number of seconds specified in `Retry-After` or until the next window reset
**No penalties**: No account suspension or additional charges
**Per key**: Yes — an organization admin updates the key through the admin API (`PUT /api/admin/api-keys/{key_id}`), and the change applies from the next request
**Organization total**: Contact Junis
**Answer**: Streaming responses count as **1 request**, regardless of how long the stream lasts.
**Example**:
* Non-streaming: 1 request = 1 complete response
* Streaming: 1 request = entire conversation stream
Streaming is more efficient for real-time chat.
**Method 1**: Check response headers (`X-RateLimit-Remaining-*`)
**Method 2**: Set up logging in your application (see Best Practices)
**Method 3**: Monitor 429 error rates in your application metrics
***
## Next Steps
Learn about API keys and scopes
Handle 429 and other API errors
Start building with streaming
Manage chat sessions efficiently
# Sessions API
Source: https://docs.junis.ai/api-reference/sessions
Retrieve conversation sessions and messages
## Overview
The Sessions API allows you to **retrieve conversation history** and access message details. All sessions are scoped to your organization and the API key creator.
Sessions are automatically created when you send messages via [Chat Completions API](/api-reference/chat-completions).
Building your own product on Junis? Add `on_behalf_of` to read the sessions of a user
you provisioned, and use `DELETE` to purge their data when they leave. See
[User Provisioning](/api-reference/user-provisioning).
### Required Scopes
All Sessions API endpoints require specific API key scopes:
| Endpoint | Required Scope |
| - | - |
| `GET /api/external/sessions` | `sessions:read` |
| `GET /api/external/sessions/{session_id}/messages` | `sessions:messages` |
| `GET /api/external/sessions/{session_id}/status` | `sessions:read` |
| `DELETE /api/external/sessions/{session_id}` | `sessions:delete` |
Adding `on_behalf_of` to any of them also requires `users:delegate`.
`sessions:delete` is opt-in and is never part of a default key. Grant it from
**Dashboard → API Keys → New Key** by ticking **"Connect users from my own app"**.
When creating an API key in the Admin panel, ensure you enable the appropriate scopes. Without the required scopes, requests will return a `403 Forbidden` error.
***
## Endpoints
| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/api/external/sessions` | List all sessions (paginated) |
| `GET` | `/api/external/sessions/{session_id}/messages` | Get session messages (paginated) |
| `GET` | `/api/external/sessions/{session_id}/status` | Get session processing status |
| `DELETE` | `/api/external/sessions/{session_id}` | Permanently delete a session and its messages |
***
## List Sessions
Retrieve all sessions created by your API key.
### Request
```bash theme={null}
GET /api/external/sessions
```
**Query Parameters:**
| Parameter | Type | Default | Description |
| - | - | - | - |
| `page` | integer | 1 | Page number (1-indexed) |
| `page_size` | integer | 20 | Items per page (max: 100) |
| `on_behalf_of` | string | — | List the sessions of a user your organization provisioned. Requires `users:delegate`. Omit for your own key's sessions. |
| `include_hidden` | boolean | `true` | Set to `false` to leave out sessions created with `session_hidden` — useful when the list is shown to the user. |
Sessions are automatically sorted by `last_update_time` (most recent first). Custom sorting is not currently supported.
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"sessions": [
{
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"created_at": "2026-01-01T10:00:00Z",
"last_update_time": "2026-01-01T15:30:00Z",
"is_active": true,
"message_count": 12
},
{
"session_id": "660f9511-f39c-52e5-b827-557766551111",
"created_at": "2026-01-01T14:20:00Z",
"last_update_time": "2026-01-01T16:45:00Z",
"is_active": false,
"message_count": 8
}
],
"total": 45,
"page": 1,
"page_size": 20,
"has_next": true
}
```
### Response Fields
| Field | Type | Description |
| - | - | - |
| `sessions` | array | Array of session objects |
| `sessions[].session_id` | string | Unique session ID (UUID) |
| `sessions[].created_at` | string | ISO 8601 timestamp |
| `sessions[].last_update_time` | string | Last activity timestamp |
| `sessions[].is_active` | boolean | Whether session is active |
| `sessions[].message_count` | integer | Number of messages in session |
| `total` | integer | Total number of sessions |
| `page` | integer | Current page number |
| `page_size` | integer | Items per page |
| `has_next` | boolean | Whether there are more pages |
### cURL Example
```bash theme={null}
curl -X GET 'https://api.junis.ai/api/external/sessions?page=1&page_size=20' \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE"
```
***
## Get Session Messages
Retrieve all messages from a specific session.
### Request
```bash theme={null}
GET /api/external/sessions/{session_id}/messages
```
**Path Parameters:**
| Parameter | Type | Required | Description |
| - | - | - | - |
| `session_id` | string | Yes | Session UUID |
**Query Parameters:**
| Parameter | Type | Default | Description |
| - | - | - | - |
| `page` | integer | 1 | Page number (1-indexed) |
| `page_size` | integer | 50 | Items per page (max: 200) |
| `on_behalf_of` | string | — | Read a session belonging to a user your organization provisioned. Requires `users:delegate`. |
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"messages": [
{
"event_id": "evt_001",
"event_type": "user_message",
"content": "What is Junis?",
"agent": null,
"created_at": "2026-01-01T10:00:00Z",
"visibility": "visible"
},
{
"event_id": "evt_002",
"event_type": "assistant_message",
"content": "Junis is a production-ready Multi-Agent AI platform...",
"agent": "Orchestrator",
"created_at": "2026-01-01T10:00:05Z",
"visibility": "visible"
},
{
"event_id": "evt_003",
"event_type": "user_message",
"content": "How do I create an agent?",
"agent": null,
"created_at": "2026-01-01T10:05:00Z",
"visibility": "visible"
},
{
"event_id": "evt_004",
"event_type": "assistant_message",
"content": "To create an agent, follow these steps...",
"agent": "Orchestrator",
"created_at": "2026-01-01T10:05:08Z",
"visibility": "visible"
}
],
"total": 12,
"page": 1,
"page_size": 50,
"has_next": false
}
```
### Message Object
| Field | Type | Description |
| - | - | - |
| `event_id` | string | Unique message ID |
| `event_type` | string | Message type (`user_message`, `assistant_message`) |
| `content` | string | Message text content |
| `agent` | string or null | Agent name (for assistant messages) |
| `created_at` | string | ISO 8601 timestamp |
| `visibility` | string | Message visibility (`visible`, `hidden`) |
Messages are always returned in chronological order (oldest first).
### cURL Example
```bash theme={null}
curl -X GET 'https://api.junis.ai/api/external/sessions/550e8400-e29b-41d4-a716-446655440000/messages?page=1&page_size=50' \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE"
```
***
## Get Session Processing Status
Check if a session is currently processing a message.
### Request
```bash theme={null}
GET /api/external/sessions/{session_id}/status
```
**Path Parameters:**
| Parameter | Type | Required | Description |
| - | - | - | - |
| `session_id` | string | Yes | Session UUID |
**Query Parameters:**
| Parameter | Type | Default | Description |
| - | - | - | - |
| `on_behalf_of` | string | — | Check a session belonging to a user your organization provisioned. Requires `users:delegate`. |
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"is_processing": true,
"started_at": "2026-01-01T15:30:00Z",
"current_agent": "Orchestrator",
"elapsed_seconds": 2.5
}
```
**When not processing:**
```json theme={null}
{
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"is_processing": false
}
```
**When timed out (30 minutes):**
```json theme={null}
{
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"is_processing": false,
"timed_out": true,
"message": "Request took too long and was automatically cancelled"
}
```
### Response Fields
| Field | Type | Description |
| - | - | - |
| `session_id` | string | Session UUID |
| `is_processing` | boolean | Whether session is currently processing |
| `started_at` | string (optional) | ISO 8601 timestamp (if processing) |
| `current_agent` | string (optional) | Active agent name (if processing) |
| `elapsed_seconds` | float (optional) | Time elapsed (if processing) |
| `timed_out` | boolean (optional) | Whether request timed out (if timeout occurred) |
| `message` | string (optional) | Status message (if timeout occurred) |
Sessions automatically timeout after 30 minutes of processing.
### cURL Example
```bash theme={null}
curl -X GET 'https://api.junis.ai/api/external/sessions/550e8400-e29b-41d4-a716-446655440000/status' \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE"
```
***
## Delete Session
Permanently delete a session together with its messages and stored state.
This cannot be undone. There is no soft delete and no recovery window.
### Request
```bash theme={null}
DELETE /api/external/sessions/{session_id}
```
**Path Parameters:**
| Parameter | Type | Required | Description |
| - | - | - | - |
| `session_id` | string | Yes | Session UUID |
**Query Parameters:**
| Parameter | Type | Default | Description |
| - | - | - | - |
| `on_behalf_of` | string | — | Delete a session belonging to a user your organization provisioned. Requires `users:delegate`. |
### Response
**Status Code:** `200 OK`
```json theme={null}
{
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"deleted": true
}
```
### cURL Example
```bash theme={null}
curl -X DELETE \
"https://api.junis.ai/api/external/sessions/550e8400-e29b-41d4-a716-446655440000" \
-H "X-API-Key: jns_live_YOUR_API_KEY"
```
Deletions are recorded in your organization's audit log.
***
## Error Handling
### Common Errors
```json 404 Not Found theme={null}
{
"detail": "Session not found or access denied"
}
```
```json 400 Bad Request theme={null}
{
"detail": {
"page_size": ["ensure this value is less than or equal to 100"]
}
}
```
## Best Practices
* **Use Pagination**: Always use pagination with appropriate `page_size` (max: 100 for sessions, 200 for messages) to avoid timeouts
* **Cache Session Data**: Implement caching to reduce API calls (recommended TTL: 5 minutes)
* **Poll Status for Long-Running Requests**: Use the status endpoint to check if a session is still processing
* **Handle Errors**: Check for 404 (session not found) and 400 (invalid parameters) responses
***
## Next Steps
Send messages and create sessions
Learn about API key authentication
Understand rate limiting
# User Provisioning
Source: https://docs.junis.ai/api-reference/user-provisioning
Provision end-user accounts and organization memberships for partner integrations
## Overview
If you are building your own product on Junis, your users should each get their own
Junis account — so their conversations, history, and memory stay separate — without
anyone having to sign up for Junis. User Provisioning does exactly that: you create an
account for each of your users, run everything on their behalf, and pay for all of it
from one place.
You never share your API key with your users, and they never see Junis.
### The lifecycle
One call with their email. Idempotent, so you can call it every login.
Push API keys straight in, or hand the person a **connect link** for anything that
needs their sign-in (Gmail, Slack). Your agents then act as that user.
Add `on_behalf_of` to your normal calls. Sessions and memory belong to them.
The same parameter works on the session read endpoints.
Purge their conversations permanently, so you can honour your own deletion promise.
**Billing never moves.** Subscription checks and credit charges always stay with the
user behind your API key, no matter whose name a call runs under. Your users do not
need a Junis subscription — or a Junis account they ever log into.
Provisioned accounts do not receive the signup credit bonus and have no memory profile
until the person actually signs in with Google themselves. If they ever do, their
Google account links to the one you created and their membership activates — nothing
is lost or duplicated.
***
## Getting a key
Create the key from **Dashboard → API Keys → New Key** and tick
**"Connect users from my own app"** before generating it. That checkbox is what turns an
ordinary key into a provisioning key.
| Checkbox | Scopes granted |
| - | - |
| Unchecked (default) | The 8 standard scopes — `orchestrator:*`, `sessions:*`, `agents:*`, `content:*` |
| Checked | The 8 standard scopes **plus** `users:provision`, `users:delegate`, `sessions:delete` and `users:credentials` |
| Scope | What it lets you do |
| - | - |
| `users:provision` | Create accounts for your users |
| `users:delegate` | Run and read as one of those users |
| `sessions:delete` | Delete their conversations |
| `users:credentials` | Store API credentials on their behalf (write-only), issue/revoke connect links, and read connect status |
Scopes are frozen into a key when it is created. If you already have a provisioning
key from before `users:credentials` existed, generate a new one to get it — existing
keys are not upgraded.
Only organization owners and admins can open that page, which is also the requirement
for using these scopes — so a key created there is always eligible. Provisioning keys
are named `provisioning-key-…` and carry a **User provisioning** label in the key list,
so you can tell them apart later.
This key can create accounts in your organization, run chats as its members, and
delete conversations. Keep it on your server — never ship it in a browser, a mobile
app, or a public repository.
***
## Endpoint
```
POST https://api.junis.ai/api/external/management/users
```
### Authentication
Include your API key in the `X-API-Key` header:
```bash theme={null}
X-API-Key: jns_live_YOUR_API_KEY_HERE
```
### Request Parameters
| Parameter | Type | Required | Description |
| - | - | - | - |
| `email` | string | **Yes** | Email of the user to provision (normalized to lowercase) |
| `name` | string | No | Display name (defaults to the email local part) |
| `organization_id` | string | **Yes** | Must match the organization your API key resolves to |
### Example Request
```bash theme={null}
curl -X POST https://api.junis.ai/api/external/management/users \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"email": "alice@example.com",
"name": "Alice",
"organization_id": "org-uuid"
}'
```
### Response
```json theme={null}
{
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"email": "alice@example.com",
"created": true,
"delegable": true,
"membership_status": "pending"
}
```
| Field | Type | Description |
| - | - | - |
| `user_id` | string | Junis user ID — usable as `on_behalf_of` in Chat Completions |
| `email` | string | Normalized email |
| `created` | boolean | `true` if a new account was created, `false` if it already existed |
| `delegable` | boolean | `true` if you can act on behalf of this account (see below) |
| `membership_status` | string | `pending` (awaiting first login) or `active` |
### When the Email Belongs to an Existing Junis User
If the email already belongs to a **real Junis account** (someone who has signed in
themselves), the account is **not** attached to your organization and cannot be delegated:
```json theme={null}
{
"email": "existing-user@example.com",
"created": false,
"delegable": false
}
```
Only the email is echoed back — no user ID or membership information is returned.
Handle this case in your integration: invite the user to your organization from the
dashboard instead, or skip Junis-linked features for that user.
***
## Behavior
If a user with the email already exists, it is returned unchanged (`created: false`).
Otherwise a new account is created with `google_id = NULL` — no signup bonus,
no memory profile (both happen on the real first login).
If the organization has no membership row for this email, a `pending` invite with the
`member` role is created — identical to inviting the user from the dashboard.
When the user signs in with Google using that email, the account is linked to their
Google identity, the pending membership is activated, and the invited organization
becomes their current organization.
The endpoint is **idempotent** — calling it repeatedly for the same email returns the
same user and membership state.
***
## Errors
| Status | Code | Description |
| - | - | - |
| `403` | `insufficient_scopes` | API key lacks the `users:provision` scope |
| `403` | — | Key's organization does not match `organization_id`, or the backing user is not an org admin/owner |
| `422` | — | Invalid email address |
***
## Storing API Credentials for a User
Most useful automations need to call something on the person's behalf — their Notion,
their Google Sheets, their store. Those calls need *their* key, not yours.
Push the credential into their account once, and every agent that runs for them uses it
automatically. They never paste a key into a chat, and the key never reaches the model.
### Endpoints
```
GET https://api.junis.ai/api/external/management/users/{user_id}/secrets
PUT https://api.junis.ai/api/external/management/users/{user_id}/secrets/{NAME}
DELETE https://api.junis.ai/api/external/management/users/{user_id}/secrets/{NAME}
```
Requires the `users:credentials` scope and an owner/admin key.
### Store or rotate
`PUT` is idempotent — the same call creates a credential the first time and rotates it
every time after.
```bash theme={null}
curl -X PUT \
"https://api.junis.ai/api/external/management/users/USER_ID/secrets/NOTION_TOKEN" \
-H "X-API-Key: jns_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "value": "secret_xxx", "description": "Customer Notion token" }'
```
```json theme={null}
{ "success": true, "user_id": "USER_ID", "name": "NOTION_TOKEN", "scope": "private" }
```
### Check what they have
Returns names only — never values. Use it to work out what is still missing and prompt
for it in your own onboarding UI.
```bash theme={null}
curl "https://api.junis.ai/api/external/management/users/USER_ID/secrets" \
-H "X-API-Key: jns_live_YOUR_KEY"
```
```json theme={null}
{
"user_id": "USER_ID",
"secrets": [
{
"id": 12,
"name": "NOTION_TOKEN",
"description": "Customer Notion token",
"created_at": "2026-08-21T04:10:00+00:00",
"updated_at": "2026-08-21T04:10:00+00:00"
}
]
}
```
### Delete
Do this when the person leaves, alongside deleting their conversations.
```bash theme={null}
curl -X DELETE \
"https://api.junis.ai/api/external/management/users/USER_ID/secrets/NOTION_TOKEN" \
-H "X-API-Key: jns_live_YOUR_KEY"
```
**Stored values can never be read back.** There is no endpoint, on any key, that
returns a credential's value — listing gives you names only. If your key leaks, the
credentials already stored with it cannot be pulled out.
**Only accounts you provisioned.** If the email already belonged to a real Junis user
(`delegable: false`), this returns `403`. Nobody — not even an organization owner —
can write into a real person's private vault. That rule is what makes the vault
trustworthy, so it has no exceptions.
### Rules
| Rule | Value |
| - | - |
| Name format | Uppercase letters, digits, underscore. Must start with a letter — `NOTION_TOKEN` |
| Value size | 8 KB max (fits PEM keys and service-account JSON) |
| Count | 200 credentials per user, per organization |
| Rotation | Always allowed, even at the count limit |
| Organizations | Separate vaults — the same person can hold a different key in each org |
### Errors
| Status | Code | Description |
| - | - | - |
| `400` | — | Invalid name — must be uppercase letters, digits, underscore |
| `400` | — | The 200-credential limit is reached (rotating an existing name still works) |
| `403` | `insufficient_scopes` | Key lacks `users:credentials` |
| `403` | — | Target is a real account, not one your organization provisioned |
| `403` | — | Target was provisioned by a different organization |
| `404` | — | User not found, or (on `DELETE`) no credential with that name |
| `422` | — | Body validation failed — empty `value`, or a value longer than 8 KB |
***
## Listing Your Provisioned Users
Reconcile against your own database, or build an internal admin view. **Only accounts your
organization provisioned appear** — never real Junis users.
```bash theme={null}
curl "https://api.junis.ai/api/external/management/users?limit=50&offset=0" \
-H "X-API-Key: jns_live_YOUR_KEY"
```
```json theme={null}
{
"users": [
{ "user_id": "usr_1", "email": "customer@example.com", "name": "Customer", "created_at": "2026-08-21T04:00:00+00:00" }
],
"total": 1,
"limit": 50,
"offset": 0
}
```
Requires `users:provision`.
***
## Connect Links — let the person connect what the squad needs
An API key you can push in directly (§ above). **Gmail, Slack, Google Calendar and other
OAuth apps you cannot** — those credentials only exist after the person clicks "Allow" on
the provider's own consent screen. And even for plain API keys, it is better when the
person's key never passes through your servers.
A **connect link** solves both with one flow: you request a link for a squad, open it in a
webview, and the person connects (or disconnects) everything on one page. OAuth redirects,
key entry and encryption are handled for you — your app needs no callback page and no OAuth
code. The page carries no branding other than what the person is connecting to.
One call with the squad's `agent_id`. The page shows only what **that squad** needs.
The person connects each item. OAuth apps redirect out and come back automatically.
Via your `return_url` — even if they only connected some items.
### What the page shows
Pass the squad's root agent (`agent_id`, from `GET /management/agents`). The page is built
from what is configured on that agent and its sub-agents:
| Section | Items |
| - | - |
| **Accounts** | Personal Composio integrations linked to the squad (`connection_type: "personal"`) — Gmail, Notion, … |
| **Credentials** | MCP platforms linked to the squad that need a personal credential. Platforms your organization already provides a key for are not shown |
| **API keys** | Personal keys the squad's instructions reference as `{cred:private:NAME}` |
If the squad changes later, existing links follow the new configuration.
### Check what is needed — and what is done
Use the same endpoint before issuing a link (is there anything to connect?) and after (did
they finish?). It is generated by the same code as the page, so what you see is what the
person sees.
```bash theme={null}
curl "https://api.junis.ai/api/external/management/users/USER_ID/connect-status?agent_id=AGENT_ID" \
-H "X-API-Key: jns_live_YOUR_KEY"
```
```json theme={null}
{
"user_id": "USER_ID",
"agent_id": "AGENT_ID",
"all_connected": false,
"items": [
{ "kind": "composio", "id": "3f9c…", "name": "Gmail", "auth_scheme": "OAUTH2",
"connected": false, "fields": [] },
{ "kind": "mcp", "id": "notion-mcp", "name": "Notion", "connected": true,
"fields": [ { "name": "NOTION_TOKEN", "type": "text", "required": true } ] },
{ "kind": "secret", "id": "HUNTER_API_KEY", "name": "HUNTER_API_KEY", "connected": false,
"description": "Used by hunter_api", "fields": [ { "name": "value", "required": true } ] }
],
"skipped": [
{ "kind": "mcp", "id": "replicate-global", "name": "Replicate", "reason": "provided_by_system" }
]
}
```
`skipped` explains what is **not** on the page and why: `provided_by_organization` /
`provided_by_system` (already works without the person doing anything), or
`no_credential_schema` (the platform does not declare which values it needs — the squad's
admin adds that in the platform settings and the item appears), or `too_many_items` (more
than 20 of one kind; trim the squad). An item with no declared fields stays hidden **even
when already connected**: the page cannot store that value, so it does not offer to remove
it either — that would be a one-way door. Items we provision automatically never appear at
all, since there is nothing for the person to enter. Stored values are never returned —
only `connected` and field **names**.
### Request a link
```bash theme={null}
curl -X POST \
"https://api.junis.ai/api/external/management/users/USER_ID/connect-sessions" \
-H "X-API-Key: jns_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "AGENT_ID",
"return_url": "myapp://connect-done",
"ttl_minutes": 30
}'
```
```json theme={null}
{
"url": "https://junis.ai/connect/xNq7...",
"expires_at": "2026-09-07T05:00:00+00:00",
"user_id": "USER_ID",
"agent_id": "AGENT_ID",
"items": { "composio": [], "mcp": [] }
}
```
| Field | Notes |
| - | - |
| `agent_id` | The squad's root agent. **Recommended.** |
| `composio` / `mcp` | Explicit mode: list catalog ids yourself (`GET /management/composio/integrations` → `id`, `GET /management/mcp-platforms` → `platform_id`). Cannot be combined with `agent_id` (`400`). Kept for compatibility |
| `return_url` | Optional. `https://…` or a custom app scheme like `myapp://done`. `http://` and `javascript:`/`data:`/`file:` are rejected |
| `ttl_minutes` | Default `30`, max `1440` |
Requires `users:credentials`, and the target must be an account you provisioned. If the
squad has nothing for the person to connect, the request returns `400` with the same
`skipped` list as `connect-status`.
**The token in `url` is shown once.** Only its hash is stored, so the link cannot be
looked up again — request a new one instead. Treat the URL like a password: it is the
only thing standing between a stranger and that user's connect page.
### Open it in a webview
Fetch the link **at the moment the person taps your "Connect" button**, then open it. Do
not pre-generate links — a 30 minute lifetime is what keeps a leaked URL harmless.
```
[ Connect accounts ] → POST …/connect-sessions → open url in webview
```
| Item | What the person sees |
| - | - |
| Gmail, Slack (OAuth) | **Connect** → provider consent → back to the page, marked connected |
| Notion, Airtable (API key) | A key field → **Save** |
| MCP credentials | One field per value the platform declares, by name → **Save** |
| API keys | A key field → **Save** |
| Anything connected | **Disconnect** |
The page shows **All set** when everything is connected, and a button back to your app
whenever you set `return_url` — including when only some items are done, so the person can
come back and finish later.
### Revoke links
When a person leaves your app, or you suspect a link leaked, invalidate every live link for
that account at once. Idempotent.
```bash theme={null}
curl -X DELETE \
"https://api.junis.ai/api/external/management/users/USER_ID/connect-sessions" \
-H "X-API-Key: jns_live_YOUR_KEY"
```
```json theme={null}
{ "user_id": "USER_ID", "revoked": 1 }
```
### What the page can and cannot do
The link grants exactly one thing: **connecting and disconnecting that one user's items
for that one squad.** It cannot read data, cannot reach other users, cannot touch items
outside the squad, and cannot reveal any stored value. The page never shows your
organization, the user's id, or their email.
* Expired, revoked and non-existent links all return the same response — a link cannot be
probed for validity.
* Reusable until it expires, because an OAuth round trip has to come back to it.
* Connecting or disconnecting refreshes that user's agent tools immediately.
### Errors
| Status | Code | Description |
| - | - | - |
| `400` | — | `agent_id` combined with `composio`/`mcp`, nothing to connect (see `skipped`), more than 20 explicit items, bad `return_url`, or an unknown toolkit id |
| `403` | `insufficient_scopes` | Key lacks `users:credentials` |
| `403` | — | Target is a real account, or was provisioned by another organization |
| `404` | — | User not found, or `agent_id` is not an active agent of your organization (global agents are accepted) |
***
## Using a Stored Credential in an Agent
Reference the credential by **name** in the agent's instruction. Junis substitutes the
calling user's own value at request time and sends it as an HTTP header — it never
enters the prompt, the model's context, or the conversation log.
```
[CUSTOM_API_CONFIG]
{
"apis": [{
"name": "notion",
"endpoint": "https://api.notion.com/v1/search",
"method": "POST",
"api_key_header": "Authorization",
"api_key": "Bearer {cred:private:NOTION_TOKEN}",
"headers": { "Notion-Version": "2022-06-28" }
}]
}
[/CUSTOM_API_CONFIG]
```
| Syntax | Resolves to |
| - | - |
| `{cred:private:NAME}` | The **calling user's** own credential |
| `{cred:NAME}` | A single organization-wide credential shared by everyone |
The two never substitute for each other. If a user has no credential with that name,
the call is **not sent** — the tool returns an error naming what is missing, instead of
quietly falling back to an organization key.
The config is read from the instruction of **the agent that owns the
`custom_api_call` tool** — not from the orchestrator. If a sub-agent makes the call,
the block belongs in that sub-agent's instruction. Giving one dedicated agent the tool
and the config keeps it out of your orchestrator's context on every turn.
### Writing it into an agent
Owner/admin only, with a **user-level** key carrying `agents:manage`. Read the current
instruction, append your block, and write the whole thing back — `instruction` is
replaced wholesale, not appended to.
```bash theme={null}
# 1. Read the current instruction
curl "https://api.junis.ai/api/external/management/agents/AGENT_ID" \
-H "X-API-Key: jns_user_YOUR_KEY"
# 2. Write back the combined instruction (and the tool, if not already assigned)
curl -X PUT "https://api.junis.ai/api/external/management/agents/AGENT_ID" \
-H "X-API-Key: jns_user_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"instruction": "\n\n[CUSTOM_API_CONFIG]...[/CUSTOM_API_CONFIG]",
"tools": ["custom_api_call"]
}'
```
`instruction` and `tools` are both replaced in full. Fetch, merge, then write — and
have only one process doing it, or two concurrent writers will overwrite each other.
An organization-level provisioning key **cannot** do this — it does not carry
`agents:manage`. Use a user-level key created by an owner or admin from
**Settings → API Keys**. The two keys have different jobs: the provisioning key
manages accounts and their credentials, the user-level key manages agents.
***
## Users Managing Their Own Credentials
If the person signs into Junis themselves, they manage their own credentials — you do
not need to push anything.
* **In the app:** **Team → MCP & Keys → My Keys**. Open to every member, including
those with the MEMBER role.
* **Over the API:** with their own user-level key carrying `secrets:manage`.
```
GET https://api.junis.ai/api/external/management/me/secrets
PUT https://api.junis.ai/api/external/management/me/secrets/{NAME}
DELETE https://api.junis.ai/api/external/management/me/secrets/{NAME}
```
```bash theme={null}
curl -X PUT "https://api.junis.ai/api/external/management/me/secrets/NOTION_TOKEN" \
-H "X-API-Key: jns_user_THEIR_KEY" \
-H "Content-Type: application/json" \
-d '{ "value": "secret_xxx" }'
```
These endpoints accept **user-level keys only**. An organization-level key is rejected
with `403`, because such a key resolves to whoever created it — accepting it would let
every holder of that key edit that person's private vault.
Same rules as above: names only on read, 8 KB per value, 200 per organization, and no
way to read a value back.
***
## Running as a Provisioned User
Take the `user_id` from the provisioning response and pass it as `on_behalf_of`. It
works on both invocation endpoints and needs the `users:delegate` scope:
| Endpoint | Use it for |
| - | - |
| [`POST /v1/chat/completions`](/api-reference/chat-completions) | Your main assistant — routed through the orchestrator |
| [`POST /agents/{id}/completions`](/api-reference/agents) | A single agent, called directly |
```json theme={null}
{
"model": "junis",
"messages": [{ "role": "user", "content": "How did I sleep last week?" }],
"on_behalf_of": "USER_ID"
}
```
Sessions, messages, and memory belong to that user. Subscription checks and credit
charges stay with your API key's own user. Delegation works before their first login,
so you can start using an account the moment you create it.
### Keeping one-off calls out of their history
Both endpoints accept `session_hidden: true`. Use it for calls that are part of your
product's machinery rather than a conversation the user should see later — scoring,
classification, background analysis:
```json theme={null}
{
"message": "Classify this entry",
"on_behalf_of": "USER_ID",
"session_hidden": true
}
```
Hidden sessions still belong to the user and still bill to you; they are simply left out
when you list a user's conversations with `include_hidden=false`.
***
## Showing a User Their History
The same `on_behalf_of` parameter works on the session read endpoints, so you can build
a history view inside your own product:
```bash theme={null}
# Their conversations, newest first
curl "https://api.junis.ai/api/external/sessions?on_behalf_of=USER_ID&include_hidden=false" \
-H "X-API-Key: jns_live_YOUR_KEY"
# One conversation, in full
curl "https://api.junis.ai/api/external/sessions/SESSION_ID/messages?on_behalf_of=USER_ID" \
-H "X-API-Key: jns_live_YOUR_KEY"
# Is a reply still being generated?
curl "https://api.junis.ai/api/external/sessions/SESSION_ID/status?on_behalf_of=USER_ID" \
-H "X-API-Key: jns_live_YOUR_KEY"
```
`include_hidden=false` leaves out the one-off calls you marked with `session_hidden`, so
the list matches what your user would expect to see. It defaults to `true`.
Omit `on_behalf_of` entirely and you get your own key's sessions, exactly as before.
**Reading is narrower than running.** On these endpoints `on_behalf_of` only accepts
accounts **your organization provisioned**. Chat completions also let you run as a real
member of your organization, but reading or deleting their conversations never works —
a provisioning key can't pull a real person's chat history.
***
## Deleting a User's Data
When someone deletes their account in your product, delete their Junis conversations
too. This is what lets you honour a "we delete everything" promise. Requires the
`sessions:delete` scope, granted by the same checkbox.
```bash theme={null}
curl -X DELETE \
"https://api.junis.ai/api/external/sessions/SESSION_ID?on_behalf_of=USER_ID" \
-H "X-API-Key: jns_live_YOUR_KEY"
```
```json theme={null}
{ "session_id": "abc123", "deleted": true }
```
The session, its messages, and its stored state are removed permanently — there is no
recovery. To purge a user completely, list their sessions with `include_hidden=true`
(the default, so you catch the hidden ones too) and delete each one.
Every deletion is recorded in your organization's audit log with who deleted it, whose
session it was, and whether the call was delegated.
### Delegation errors
| Status | Code | Description |
| - | - | - |
| `403` | `insufficient_scopes` | Key lacks `users:delegate` (read) or `sessions:delete` (delete) |
| `403` | `delegation_target_not_found` | `on_behalf_of` user does not exist or is inactive |
| `403` | `delegation_target_not_member` | Target is not an active or pending member of your organization |
| `403` | `delegation_target_not_provisioned` | Target is a real account, not one your organization provisioned |
| `404` | — | Session does not exist, or does not belong to the target user |
# Warmup
Source: https://docs.junis.ai/api-reference/warmup
Build an organization/user orchestrator (and optionally one agent) ahead of the first message
## Overview
Junis builds your organization's agents lazily, per **(organization, user)**, on the server that receives the request, and keeps them in memory while they are in use. The first message of a newly provisioned user therefore pays that build before the first token: a fraction of a second for a small squad, several seconds for squads with many tools or integrations.
`POST /v1/warmup` performs that build **without any LLM call** (no tokens, no charge) so the first message finds everything ready. Requests that carry the same API key are routed to the same server, so the warmed instance is the one your next message uses.
**When to call it**: right after provisioning a user ([User Provisioning](/api-reference/user-provisioning)), or after a long idle period. Warmed instances are released after about an hour of inactivity and after server restarts or deployments; calling warmup again is always safe.
***
## Endpoint
```
POST https://api.junis.ai/api/external/v1/warmup
```
### Authentication
| Scope | When |
| - | - |
| `orchestrator:invoke` | always |
| `users:delegate` | when `on_behalf_of` is set |
| `agents:invoke` | when `agent_id` is set |
### Request Body
The body is optional. `curl -X POST …/v1/warmup` with no body (or `{}`) warms the orchestrator for the API key's own user.
| Field | Type | Required | Description |
| - | - | - | - |
| `on_behalf_of` | string | No | Warm the orchestrator for this provisioned user (same rules as in Chat Completions: active or pending member of your organization). Omit to warm for the API key's own user. |
| `agent_id` | string | No | Also pre-build this agent (UUID of an active agent in your organization). Useful before the first [`POST /agents/{agent_id}/completions`](/api-reference/agents) call or the first `target_agent_id` turn. Unknown/foreign → `404 agent_not_found`, inactive → `400 agent_inactive`. |
### Request Example
```bash cURL theme={null}
curl -X POST https://api.junis.ai/api/external/v1/warmup \
-H "X-API-Key: jns_live_YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"on_behalf_of": "9f1c…",
"agent_id": "785aba85-caf1-4e22-8ca9-841093e611d6"
}'
```
### Response
```json theme={null}
{
"status": "built",
"took_ms": 412,
"organization_id": "603da21e-…",
"user_id": "9f1c…",
"sub_agents": 8,
"tools": 18,
"agent_id": "785aba85-caf1-4e22-8ca9-841093e611d6",
"agent_ready": true
}
```
| Field | Type | Description |
| - | - | - |
| `status` | string | `built` — the build ran now (or joined a build already in progress); `warm` — already cached |
| `took_ms` | integer | Time spent building (after validation), in milliseconds |
| `organization_id` | string | Your organization |
| `user_id` | string | The user the orchestrator was warmed for |
| `sub_agents` | integer | Sub-agents attached to the orchestrator |
| `tools` | integer | Tools attached to the orchestrator |
| `agent_id` | string | The pre-built agent, when requested |
| `agent_ready` | boolean | Whether the requested agent was built and cached |
### Errors
| Status | Code | Meaning |
| - | - | - |
| `403` | `insufficient_scopes` | Missing `users:delegate` (with `on_behalf_of`) or `agents:invoke` (with `agent_id`) |
| `403` | `no_subscription` / `invalid_plan` | Same subscription gate as `POST /v1/chat/completions` (Basic or Pro required for the key's user) |
| `402` | `insufficient_credits` | Same credit gate as `POST /v1/chat/completions` (billing user's effective balance is negative) |
| `404` | `agent_not_found` | `agent_id` is not an agent of your organization |
| `400` | `agent_inactive` | The agent exists but is inactive |
| `429` | `warmup_rate_limited` | Too many **cold** builds for this API key (see below). The response carries `Retry-After` (seconds). Nothing is lost — the user's first message builds normally. |
| `500` | `internal_error` | The build failed (the next message will try again) |
Warmup is rate-limited like every other External API call. It is idempotent: calling it for an already-warm user returns `status: "warm"` immediately.
### Cold-build limits
A cold build is the expensive part of warmup, so each API key may run at most **2 cold builds at the same time** and **30 cold builds per minute** by default. Calls for users that are already warm never count and are never rejected. Warm users right before their first message (for example when they open your app) rather than warming every provisioned account in a batch — idle instances are cleaned up after a while, so a batch warmup mostly builds instances that expire unused.
# Core Concepts
Source: https://docs.junis.ai/concepts
Understanding the Junis platform architecture and key concepts
## What is the Junis platform architecture?
Junis is an AI workforce platform built on a **multi-agent architecture**: every organization runs its own orchestrator that routes requests to specialized agents, which use tools, MCP integrations, RAG knowledge bases, and persistent memory to complete tasks. This page explains the core concepts behind that architecture — agents, organizations, sessions, tools, and the systems that connect them.
***
## 1. Agents
**Agents** are the fundamental building blocks of Junis. Think of them as AI workers with specific skills and responsibilities.
### Agent Types
Individual AI worker that processes tasks using a language model
* Has its own instructions and tools
* Can call sub-agents
* Specialized for specific tasks
Executes sub-agents one after another in order
* Step-by-step processing
* Output of one becomes input of next
* Perfect for pipelines
Runs multiple sub-agents simultaneously
* Concurrent execution
* Aggregates results
* Faster for independent tasks
Repeats sub-agents until a condition is met
* Iterative processing
* Configurable max iterations
* Useful for retry logic
### Agent Hierarchy
```mermaid theme={null}
graph TD
Orchestrator[Orchestrator
Entry point for all requests]
Orchestrator -->|Routes to| Sequential[Sequential Agent
Data Pipeline]
Orchestrator -->|Routes to| Parallel[Parallel Agent
Multi-task]
Sequential --> Agent1[LLM Agent 1
Data Collection]
Sequential --> Agent2[LLM Agent 2
Data Analysis]
Sequential --> Agent3[LLM Agent 3
Report Generation]
Parallel --> Agent4[LLM Agent 4
Task A]
Parallel --> Agent5[LLM Agent 5
Task B]
Parallel --> Agent6[LLM Agent 6
Task C]
```
**Orchestrator** is a special agent that serves as the entry point for all requests. It routes messages to appropriate sub-agents based on the user's intent.
***
## 2. Multi-Tenant System
Junis is a **multi-tenant platform** where each organization has isolated data and configurations.
### Organization Isolation
```mermaid theme={null}
graph LR
System[Junis Platform]
System --> Org1[Organization A]
System --> Org2[Organization B]
System --> Org3[Organization C]
Org1 --> A1[Agents]
Org1 --> A2[Sessions]
Org1 --> A3[RAG Data]
Org2 --> B1[Agents]
Org2 --> B2[Sessions]
Org2 --> B3[RAG Data]
```
**Key Points**:
* Each organization has its own **Orchestrator**
* Agents are **scoped to organizations**
* Sessions and data are **completely isolated**
* Users can belong to multiple organizations
### Organization Roles
| Role | Permissions |
| - | - |
| **OWNER** | Full organization control, manage all settings |
| **ADMIN** | Manage agents, tools, schedules, members |
| **MEMBER** | Use agents, create sessions, upload RAG documents |
Platform-level roles (SUPER\_ADMIN, SUPPORT) are managed separately for system administration purposes.
***
## 3. Sessions
A **session** represents a conversation between a user and the Junis system. Sessions maintain context across multiple messages.
### Session Lifecycle
```mermaid theme={null}
sequenceDiagram
participant User
participant Frontend
participant API
participant Database
User->>Frontend: Send first message
Frontend->>API: POST /chat (no session_id)
API->>Database: Create new session
Database-->>API: Return session_id
API-->>Frontend: Stream response + session_id
Frontend->>Frontend: Store session_id
User->>Frontend: Send follow-up message
Frontend->>API: POST /chat (with session_id)
API->>Database: Load session context
API-->>Frontend: Stream response with context
```
**Session Properties**:
* **Persistent**: Stored in PostgreSQL database
* **Contextual**: Agents remember previous messages
* **Searchable**: Full conversation history available via API
* **Resumable**: Can continue sessions across disconnections
***
## 4. Tools
**Tools** are functions that agents can call to perform actions or retrieve information.
### Tool Categories
**Python Functions** registered in the system:
* `various_of_tools`: Useful Tools
**External services** via Model Context Protocol:
* **GitHub**: `create_repository`, `create_issue`, `list_commits`
* **Firecrawl**: `scrape_url`, `crawl_website`, `map_website`
* **Remote MCPs**: `-`
* **Local MCPs**: `-`
Dynamic toolsets from MCP servers
**Document search** tools powered by Vertex AI:
* Auto-generated per knowledge base
* Semantic search across uploaded documents
* Returns relevant excerpts with citations
* Fully managed by the system
### Tool Calling Flow
```mermaid theme={null}
sequenceDiagram
participant Agent
participant Google ADK
participant Tool
participant External Service
Agent->>Google ADK: Generate response
Google ADK->>Google ADK: Detect tool call needed
Google ADK->>Tool: Call tool with parameters
Tool->>External Service: Execute action
External Service-->>Tool: Return result
Tool-->>Google ADK: Return formatted result
Google ADK-->>Agent: Continue with result
```
***
## 5. Model Context Protocol (MCP)
**MCP** is a standard protocol for connecting AI agents to external services. Junis supports both **Remote** and **Local** MCP servers.
### MCP Architecture
```mermaid theme={null}
graph TB
Agent[LLM Agent]
Loader[Dynamic Agent Loader]
Integration[MCP Integration Layer]
Agent -->|Load MCP| Loader
Loader -->|Initialize| Integration
Integration -->|HTTP/SSE| Remote[Remote MCP Server]
Integration -->|Subprocess| Custom[Custom MCP Server]
Remote --> GitHub[GitHub API]
Remote --> Firecrawl[Firecrawl API]
subgraph "Remote MCP"
Remote
GitHub
Firecrawl
end
subgraph "Custom MCP"
Custom
end
```
### Authentication Hierarchy
MCP credentials support **multi-level authentication**. Users can configure credentials at different levels, with automatic fallback to appropriate defaults.
For detailed authentication configuration, including priority systems and credential management, refer to the MCP Integration Guide in the Admin documentation.
***
## 6. RAG (Retrieval-Augmented Generation)
**RAG** enhances agents with knowledge from your documents. When a user asks a question, the system:
1. **Searches** relevant documents using semantic search
2. **Retrieves** the most relevant excerpts
3. **Augments** the agent's prompt with this context
4. **Generates** a response informed by your documents
### RAG Workflow
```mermaid theme={null}
sequenceDiagram
participant User
participant Agent
participant RAG Tool
participant Vertex AI Search
participant GCS
User->>Agent: Ask question
Agent->>Agent: Detect need for knowledge
Agent->>RAG Tool: Call rag_datastore_id()
RAG Tool->>Vertex AI Search: Semantic search
Vertex AI Search->>GCS: Fetch document chunks
GCS-->>Vertex AI Search: Return chunks
Vertex AI Search-->>RAG Tool: Return relevant excerpts
RAG Tool-->>Agent: Formatted context
Agent-->>User: Answer with citations
```
### DataStore Structure
```
Organization: Acme Corp
├── DataStore: salesforce_brands
│ ├── brands_2026_q1.pdf
│ ├── brands_2026_q2.pdf
│ └── brands_pricing.xlsx
├── DataStore: marketing_assets
│ ├── campaign_brief.docx
│ └── creative_guidelines.pdf
└── DataStore: product_docs
├── api_reference.md
└── user_manual.pdf
```
***
## 7. Memory System
**Memory** gives your AI agents the ability to remember users across conversations, building a personalized understanding over time.
### Memory Tiers
```mermaid theme={null}
graph LR
Chat[Conversations] -->|After each session| D1[Daily Memory]
D1 -->|Monthly| D30[Monthly Summary]
D30 -->|Quarterly| D90[Quarterly Summary]
D90 -->|Yearly| D365[Yearly Narrative]
Chat -->|Key facts| D0[Eternal Memory]
User[User Profile] -.->|Editable| Agent[Agent Context]
D0 -.-> Agent
D1 -.-> Agent
D30 -.-> Agent
```
| Tier | What It Stores | How It's Updated |
| - | - | - |
| **User Profile** | Name, role, preferences, expertise | Agent or user updates anytime |
| **Eternal Memory** | Permanent facts and standing instructions | Agent stores on user request |
| **Daily Memory** | Conversation summaries | Automatic after each session |
| **Monthly/Quarterly/Yearly** | Consolidated patterns and narratives | AI-powered auto-consolidation |
**Key Properties**:
* **Automatic**: Memory is collected and consolidated without manual action
* **Per-User**: Each user has isolated memory — no cross-user visibility
* **Contextual Injection**: Agents automatically load relevant memories at the start of each conversation
* **AI Consolidation**: Memories are periodically reviewed and summarized, mimicking human memory
For details on managing memory and available tools, see the [Memory System Guide](/guides/memory-system).
***
## 8. Soul & Identity
**Soul & Identity** defines your AI team's personality at the organization level. It controls how agents think and communicate.
### Two Files
**Philosophy & Thinking** — Core values, decision-making principles, reasoning style
**Expression & Persona** — Communication tone, language patterns, personality traits
### How It Works
```mermaid theme={null}
graph TD
Soul[soul.md] --> Inject[Auto-Injection]
Identity[identity.md] --> Inject
Inject --> Every[Every Agent Conversation]
```
* **Organization-scoped**: All agents inherit the same personality
* **Automatic injection**: Loaded into every conversation without manual configuration
* **Admin-only editing**: Only Admin/Owner roles can modify
* **Instant effect**: Changes apply to the next conversation
Soul & Identity defines *who* your AI is. Agent instructions define *what* each agent does. See the [Soul & Identity Guide](/guides/soul-identity).
***
## 9. Streaming
Junis supports **real-time streaming** for better user experience.
### Streaming Modes
**REST API streaming** via `/api/external/v1/chat/completions`
* HTTP with `stream: true`
* OpenAI-compatible format
* Token-by-token delivery
**Persistent connection** via `/ws/chat`
* Bidirectional communication
* Pipeline events
* File uploads
* Payment flows
### Event Types (WebSocket)
| Event Type | Description | Example |
| - | - | - |
| `stream_token` | Individual text token | `{"type": "stream_token", "token": "Hello"}` |
| `agent_routing` | Agent handoff | `{"type": "agent_routing", "from": "A", "to": "B"}` |
| `agent_started` | Agent begins processing | `{"type": "agent_started", "agent": "Analyzer"}` |
| `agent_completed` | Agent finishes | `{"type": "agent_completed", "duration_ms": 1234}` |
| `stream_complete` | Entire response complete | `{"type": "stream_complete"}` |
***
## 10. Performance Optimization
Junis uses **intelligent caching and optimization** to ensure fast, responsive agent interactions.
### Caching System
The platform implements a multi-tier caching strategy:
* **Agent Configuration**: Automatically cached for rapid access
* **Session Context**: Persisted for conversation continuity
* **External Integrations**: Connection pooling and result caching
* **Database Queries**: Query-result caching layer
### Cache Invalidation
Caches are automatically updated when:
* Agent configuration changes (Admin UI)
* Agent relationships modified (Admin UI)
* Organization settings updated
* External service credentials change
For detailed information about caching strategy, performance tuning, and optimization techniques, refer to the Operations Guide in the Admin documentation.
***
## 11. Scheduled Tasks
**Scheduled Tasks** enable automation by running agents at specific times or intervals.
### Schedule Types
Run at a specific date/time
Repeat every N minutes
Repeat every N hours
Repeat every day at specified hour
Run on specific weekdays
Execute monthly on specific day
### Schedule Execution
```mermaid theme={null}
sequenceDiagram
participant Celery Beat
participant Celery Worker
participant Orchestrator
participant Database
Celery Beat->>Celery Beat: Check due tasks
Celery Beat->>Celery Worker: Trigger task
Celery Worker->>Database: Load task config
Celery Worker->>Orchestrator: Send prompt
Orchestrator-->>Celery Worker: Response
Celery Worker->>Database: Save result
```
**Key Features**:
* Timezone-aware scheduling
* Session reuse or new session per run
* Execution history and logs
* Enable/disable without deletion
***
## Next Steps
Now that you understand the core concepts, explore these guides:
Learn how to combine agent types
Manage your platform from Claude Desktop
Persistent AI memory across conversations
Define your AI team's personality
Connect MCP and Composio platforms
Integrate programmatically
# Junis AX 프로그램
Source: https://docs.junis.ai/consulting/overview
전사 AI 전환(AX). 조직의 두뇌를 만들고 에이전트 스쿼드를 배치합니다. 보수는 성과에서만 받는 파트너십.
Junis AX는 기업의 AI 전환을 설계부터 운영까지 책임지는 파트너십 프로그램입니다. 도구를 알려주는 교육이 아니라, 회사의 데이터·지식·업무 위에 실제로 일하는 AI 조직을 만들어 함께 운영합니다. 보수는 성과에서만 받습니다.
## AX란 무엇인가: 두 개의 축
AI 전환(AX)은 두 축이 함께 갈 때 완성됩니다. 하나만 하면 반쪽입니다.
팀원 각자가 Claude Code 같은 AI 도구로 자기 일을 빠르게 하는 단계.
필요하지만, 개인의 생산성에서 멈춥니다. 사람이 나가면 노하우도 나갑니다.
회사의 데이터와 지식으로 전사 두뇌를 만들고, 그 위에 에이전트 스쿼드를
배치하는 단계. 지식이 조직에 쌓이고, 일이 사람 없이도 돌아갑니다.
비즈니스 10x는 여기서 나옵니다.
Junis AX는 **축 2를 만드는 프로그램**입니다. 축 1은 그 과정에서 자연히 따라옵니다.
## 무엇을 하나요?
레거시 파악부터 운영까지, 전 과정을 저희가 직접 수행합니다.
| | 단계 | 내용 |
| - | - | - |
| 1 | **레거시 파악 · 설계** | 현재 시스템·업무 흐름·데이터 자산을 전수 분석하고 전환 설계도를 그립니다 |
| 2 | **데이터 온톨로지** | 흩어진 데이터를 하나의 온톨로지로 통합하고, 자동 업데이트와 에이전트 연결까지 구축합니다 |
| 3 | **지식 체계** | RAG · LLM Wiki · 온톨로지로 회사가 아는 모든 것을 AI가 쓸 수 있는 형태로 만듭니다 |
| 4 | **에이전트 스쿼드 배치** | 재무 분석, 시장 인사이트, 콘텐츠 생성, 고객사 발굴, 세일즈, 신제품 기획, 위기 대응까지, 사람이 하던 일마다 전담 스쿼드를 구성합니다 |
| 5 | **레거시 툴 연결** | Slack · Google Drive · Gmail · Linear 등 이미 쓰는 도구에 그대로 연결합니다 |
| 6 | **대시보드** | 경영진과 실무진이 보는 지표를 실시간 대시보드로 구성합니다 |
| 7 | **보안 체계** | 권한 · 크레덴셜 격리 · 감사 로그까지 조직 기준에 맞게 설계합니다 |
| 8 | **운영 · 개선 · 교육** | 배치가 끝이 아닙니다. 함께 운영하며 개선하고, 팀이 스스로 다루도록 교육합니다 |
## 비용: 성과로만 과금합니다
Junis AX의 보수는 시스템이 만든 **수익의 일부**입니다.
성과가 없으면 보수도 없습니다. 저희가 성과에 집착하는 이유입니다.
시작 시 합리적 수준의 미니멈 개런티가 있습니다. **전액이 성과 수수료에서
차감**되는 구조로, 별도 컨설팅비가 아니라 성과에 대한 선지급입니다.
요약하면, 성과가 나면 컨설팅 비용은 사실상 없고, 성과가 부족하면 저희가 손해를 봅니다.
리스크는 저희 쪽에 있습니다.
## 연 매출 1,000억 기업에서 이미 돌아가고 있습니다
연 매출 1,000억 원대의 글로벌 소비재 기업에서 Junis 에이전트 스쿼드가 상시 운영되고 있습니다. 아래는 실제 운영 중인 구성입니다.
* **리뷰 분석**: 고객 리뷰를 루프로 상시 분석해 제품별 인사이트를 만듭니다
* **주간 인텔리전스**: 시장 · 자사 리포트가 매주 Slack으로 배달됩니다
* **트렌드 수집**: 카테고리 트렌드와 소셜 시그널을 상시 추적합니다
* **글로벌 채널**: 해외 마켓플레이스 인사이트와 메일 대응을 담당합니다
* **콘텐츠 발행**: 소셜 채널별 콘텐츠를 만들어 발행합니다
* **업무 연결**: 메일 · 보고 · 문서화를 잇고, 사내 지식 질의응답과 종합 비즈니스 분석까지 맡습니다
ERP · 물류(WMS) · 커머스 스토어 · 운영 DB 네 시스템을 **채팅 한 줄로 조회**합니다.
재고 · 주문 · 정산 질문에 즉답하고, 전용 동기화 파이프라인이
계정 · 상품 · BOM 데이터를 자동으로 맞춥니다.
잠재 고객사와 담당자를 자동 발굴해 온톨로지 DB에 적재하고, 매주 스케줄로
스스로 실행됩니다. 신규 파트너 후보도 상시 탐색합니다.
실시간 사용량 · 비용 대시보드, 커머스 컨트롤타워, 주간 인텔리전스 리포트.
경영진이 보는 화면까지가 구축 범위입니다.
## 시작하기
메일 한 통이면 됩니다. 현재 시스템과 목표를 알려주시면, 저희가 먼저 분석해서 찾아갑니다.
회사명과 해결하고 싶은 문제 한 줄이면 충분합니다.
Junis를 만드는 TOOEASY는 Strong Ventures · Mashup Angels · Google for Startups의
투자·지원을 받았으며, 정부 TIPS 프로그램에 선정되었습니다.
# Admin Interface Guide
Source: https://docs.junis.ai/getting-started/admin-interface
Master the Junis Admin Panel - manage agents, workflows, and team settings
## Welcome to the Admin Panel
The Junis Admin Panel is your **central command center** for managing AI agents, workflows, knowledge bases, and team settings. This no-code interface allows you to build sophisticated multi-agent systems without writing a single line of code.
**What You'll Learn:**
* Navigate the Admin interface
* Manage agents and workflows
* Configure RAG knowledge bases
* Connect MCP integrations
* Monitor usage and performance
***
## 📍 Accessing the Admin Panel
### Navigation
Navigate to [https://junis.ai](https://junis.ai) and sign in with your account
From the top navigation bar, ensure you've selected the correct organization (if you're a member of multiple organizations)
Open the **"Team Management"** dropdown in the top navigation bar. Admin features live under its menu items — for example **"Dashboard"** (usage dashboard) and **"Agent Orchestration"** (agent management)
**Two surfaces, one admin experience**: Most admin pages (Agent Orchestration, RAG Knowledge, MCP & Keys, Skills, Members, Audit, etc.) live under the **Team Management** dropdown in the top navigation bar. The usage **Dashboard** described in this guide is also opened from that same dropdown. Other guides that say "go to Agent Orchestration" mean **Team Management → Agent Orchestration**.
### Permission Levels
Different features are available based on your role:
| Feature | Member | Admin | Owner |
| - | - | - | - |
| View Dashboard | ✅ | ✅ | ✅ |
| Manage API Keys | ✅ | ✅ | ✅ |
| View Agents | ❌ | ✅ | ✅ |
| Create/Edit/Delete Agents | ❌ | ✅ | ✅ |
| Organization Settings | ❌ | ✅ | ✅ |
| Rename / Delete Organization, Billing | ❌ | ❌ | ✅ |
| System Management | ❌ | ❌ | ❌ |
**Role Hierarchy**: Owner > Admin > Member. Higher roles inherit all permissions from lower roles. Owner additionally controls organization-level settings such as renaming or deleting the organization and billing.
***
## 📊 Dashboard Overview
The Dashboard is the first page you see when entering the Admin panel. It provides **real-time insights** into your organization's AI usage.
### Quick Stats
The top section displays four key metrics:
Number of unique users in the last 24 hours
Total chat sessions created today
Currently connected WebSocket sessions (real-time)
Average response time in milliseconds (last 24 hours)
### Real-Time Metrics
The metrics section shows **live updates** via WebSocket:
* **Active Sessions**: Users currently chatting with agents
* **Recent Messages**: Message rate per minute
* **Agent Performance**: Response times by agent
* **Error Rate**: System health monitoring
Metrics refresh automatically every few seconds. Look for the **green indicator** to confirm live connection status.
### Recent Activity (Admin Only)
Admins and Super Admins can see:
* Latest chat sessions
* Which agents were used
* Session duration and message count
* User information
***
## 🤖 Agent Management
The heart of Junis - where you create, configure, and visualize your AI agent workflows.
### Two Views: List and Orchestration
**Table View** - See all agents in a sortable, hierarchical list
**Columns:**
* **Name**: Agent identifier (Orchestrator shown first)
* **Type**: LLM, Sequential, Parallel, or Loop (color-coded badges)
* **SubAgents**: Visual chips showing connected agents with order numbers
* **Description**: Agent purpose and function
* **Actions**: Edit, Relationships, Delete buttons
**Sorting**: Agents are automatically sorted by hierarchy, starting with the Orchestrator, then its sub-agents in execution order.
**Visual Workflow Builder** - Interactive node graph showing agent relationships
**Features:**
* Drag-and-drop node positioning
* Color-coded by agent type:
* **Blue**: LLM Agent
* **Purple**: Sequential Agent
* **Green**: Parallel Agent
* **Orange**: Loop Agent
* Connection lines show execution flow
* Zoom and pan controls
* Minimap for navigation
### Creating a New Agent
From the Agent Management page, click the **"+ New Agent"** button (top right)
Select how to create the agent:
* **Create New**: Build from scratch
* **Use Global Agent**: Import a public template from the Junis marketplace
```yaml Basic Settings theme={null}
Name: My New Agent
Agent Type: LLM_AGENT
Description: What this agent does
Status: Active ✅
```
```yaml Model Configuration theme={null}
Model: anthropic/claude-sonnet-5 # Or gpt, gemini, etc.
Temperature: 0.7
Max Output Tokens: 16000
Streaming: Enabled ✅
```
```yaml System Prompt theme={null}
You are a helpful assistant that...
Your responsibilities:
1. Answer user questions clearly
2. Use tools when appropriate
3. Be concise and professional
Important: Always verify information before responding.
```
* **Regular Tools**: Select Python functions (search\_database, send\_email, etc.)
* **RAG Tools**: Select knowledge bases (rag\_product\_docs, rag\_support\_kb)
* **MCP Platforms**: Select integrations (GitHub, Firecrawl)
* **Output Key**: Variable name for Sequential Agent data passing
* **Payment Settings**: Enable paid usage (wallet address, price per use)
* **Web Search**: Enable real-time web search (Gemini 2.5+/3.x, Claude, OpenRouter models)
* **Extended Thinking**: Enable reasoning tokens (Claude, Gemini 3.x, and other reasoning-capable models)
Click **"Create"** - your agent will be instantly available!
**Auto-Refresh**: The cache is automatically refreshed when you create or update agents. No manual reload needed!
### Editing an Agent
1. Click the **Edit (pencil)** icon next to the agent name
2. Modify settings in the dialog
3. Click **"Save"** to apply changes
**Toggle Switches** (instant save):
* **Active/Inactive**: Enable or disable the agent
* **Streaming**: Turn real-time streaming on/off
**Global Agents** (marked with a globe icon) **cannot be edited**. These are public templates provided by Junis. To customize, create a copy instead.
### Managing Agent Relationships
**Agent Relationships** define which sub-agents a parent agent can call.
Click the **GitBranch icon** next to the agent name
See two sections:
* **Sub-Agents**: Agents this one calls (with order index)
* **Parent Agents**: Agents that call this one
1. Click **"+ Add Sub-Agent"**
2. Select an agent from the dropdown
3. Set the **order index** (1 = first, 2 = second, etc.)
4. Click **"Add"**
For **Sequential Agents**, order matters!
* Click the **up/down arrows** to change execution order
* Changes save immediately
Click the **trash icon** next to a sub-agent relationship
**For Sequential Agents**: Sub-agents execute in order (1, 2, 3...). Each agent passes its output to the next via the `output_key` variable.
**For Parallel Agents**: Order doesn't matter. All sub-agents run simultaneously, and results are combined.
### Visual Workflow Builder
The **Orchestration tab** provides an interactive graph view of your agent system.
**Node Colors**:
* **Blue Gradient**: LLM Agent (performs tasks)
* **Purple Gradient**: Sequential Agent (runs agents in order)
* **Green Gradient**: Parallel Agent (runs agents simultaneously)
* **Orange Gradient**: Loop Agent (repeats until condition met)
**Connection Lines**:
* **Solid**: Direct parent-child relationship
* **Dotted (Purple)**: Sequential execution flow (left to right)
* **Dotted (Green)**: Parallel execution flow (top to bottom)
**Legend** (top of the page):
* Shows agent type meanings
* Indicates execution flow patterns
**Unconnected Agents Panel** (right side):
* Lists agents not connected to the Orchestrator
* Displays active/inactive status
* Tool Agents are automatically hidden
**Pro Tip**: Use the minimap (bottom right) to navigate large workflows. Click and drag the viewport box to move around.
### Deleting an Agent
**Deletion is permanent!** Make sure you're not deleting an agent that other agents depend on.
Before deleting, open the **Relationships dialog** to see if other agents use this one
If the agent has **parent agents**, remove those relationships first
Click the **trash icon**, confirm the deletion
**Orchestrator Protection**: You cannot delete the Orchestrator agent. It's the root of your organization's agent system.
***
## 📚 RAG Knowledge Base
**RAG (Retrieval-Augmented Generation)** allows your agents to query uploaded documents for accurate, context-aware responses.
### How It Works
```mermaid theme={null}
graph LR
A[User Question] --> B[Agent receives query]
B --> C{Has RAG tool?}
C -->|Yes| D[Search Knowledge Base]
C -->|No| E[Direct LLM response]
D --> F[Retrieve relevant documents]
F --> G[LLM generates answer with context]
G --> H[Response to user]
E --> H
```
### Uploading Documents
Navigate to **Team > RAG Knowledge** in the sidebar
**Supported Formats**:
* ✅ PDF (.pdf) - Max 50MB
* ✅ Microsoft Word (.docx) - Max 50MB
* ✅ PowerPoint (.pptx) - Max 10MB
* ✅ Plain Text (.txt) - Max 10MB
* ✅ Markdown (.md, .markdown) - Max 10MB
* ✅ HTML (.html, .htm) - Max 10MB
* ✅ JSON (.json, .jsonl) - Max 10MB
Documents are uploaded to Google Cloud Storage and indexed by Vertex AI Search. Indexing typically takes **5-60 minutes** depending on file count.
Click **"Upload Document"** and select your file
The system will:
1. Upload to Google Cloud Storage
2. Create a Vertex AI Search DataStore
3. Index the document (JSONL conversion)
4. Generate a RAG tool ID (format: `rag_{datastore_id}`)
Watch the upload progress bar. Status will change:
* **Uploading** → **Indexing** → **Completed**
### Assigning RAG Tools to Agents
Open the agent edit dialog and scroll to the **RAG Tools** section
Check the boxes for the knowledge bases this agent should access
Example:
* ✅ `rag_product_documentation`
* ✅ `rag_support_tickets_history`
* ☐ `rag_marketing_materials`
The agent can now query these documents during conversations!
### RAG Best Practices
* **Clean formatting**: Remove unnecessary headers, footers
* **Consistent structure**: Use headings, bullet points
* **Clear language**: Avoid jargon unless necessary
* **Chunk size**: Keep documents under 10MB for faster indexing
* Separate by **topic** (product docs, support tickets, policies)
* Separate by **audience** (internal vs customer-facing)
* Agents can use multiple DataStores simultaneously
* Include **keywords** in your documents
* Use **semantic search** (Vertex AI Search handles this automatically)
* Test queries with the agent to verify retrieval accuracy
***
## 🔌 MCP Integrations
**MCP (Model Context Protocol)** connects your agents to external platforms like GitHub, Firecrawl, and custom services.
### Available Platforms
Manage repos, issues, and PRs
Web scraping and crawling
**Supported platforms**: GitHub, Firecrawl, and custom MCP servers. Easily extensible for any service with MCP implementation. See the [MCP Integration Guide](/guides/mcp/overview) for details.
### Setting Up MCP Credentials
**Shared credentials for all team members**
Go to **Team > MCP** in the sidebar
Click **"Connect"** next to a platform (e.g., GitHub, Firecrawl)
* **API Key**: Enter the API key/token
* **OAuth**: Click "Authorize" to connect via OAuth
**Shared Access**: All agents in your organization can now use this platform (if granted permission).
**Your personal credentials (e.g., personal GitHub)**
Go to **Settings > User Profile** (click your avatar, then "Settings")
Click the **"MCP Credentials"** tab
* Click **"+ Add Platform"**
* Select the platform (e.g., GitHub)
* Enter your **personal API key** or authorize via OAuth
Your personal credentials **override** organization credentials for that platform
**Use Case**: Personal GitHub credentials ensure agents access **your repositories**, not organization repos. User-level credentials override organization credentials.
### Enabling MCP for an Agent
Open the agent edit dialog and scroll to the **MCP Platforms** section
Check the boxes for platforms this agent should access:
* ✅ GitHub
* ✅ Firecrawl
* ☐ Custom MCP (if configured)
Click **"Save"** to enable MCP integration for this agent
***
## 🔑 API Keys
Generate API keys to integrate Junis agents into external applications.
### Creating an API Key
Navigate to **Admin > Dashboard**, then click the **"API Keys"** tab
Click **"+ New API Key"**
```yaml API Key Settings theme={null}
Name: My Integration Key
Description: For mobile app integration
Permissions: Read/Write
Expiration: 90 days (optional)
```
**⚠️ Important**: The key is shown **only once**. Copy it immediately!
Format: `jns_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` (32 characters after prefix)
### Using an API Key
**Authentication Header**:
```bash theme={null}
X-API-Key: jns_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
**Example Request** (OpenAI-compatible):
```bash theme={null}
curl https://api.junis.ai/api/external/v1/chat/completions \
-H "X-API-Key: jns_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"model": "orchestrator",
"messages": [{"role": "user", "content": "Hello!"}]
}'
```
**OpenAI SDK Compatible**: Use the OpenAI Python SDK with `base_url="https://api.junis.ai/api/external/v1"` for seamless integration.
### Managing API Keys
* See all API keys for your organization
* Check **last used** timestamp
* View **permissions** and **expiration**
Click **"Revoke"** next to a key to immediately disable it (cannot be undone)
Best practice: **Rotate keys every 90 days**
1. Create a new key
2. Update your applications
3. Revoke the old key
***
## ⚙️ Organization Settings
**Admin/Owner Only**: This section is only accessible to Admins and Super Admins.
Navigate to **Admin > Dashboard > Settings tab** to configure organization-wide options.
### Team Settings
* **Name**: Your organization's display name
* **Slug**: URL-friendly identifier (e.g., `acme-corp`)
* **Logo**: Upload a logo image (PNG, JPG, max 2MB)
* **Description**: Public description (shown on showcase page)
* **Enable Public Profile**: Make your organization discoverable on junis.ai/showcase
* **Welcome Suggestions**: Customize suggested prompts for visitors
* **Featured Agents**: Select which agents to showcase publicly
* **Company Wallet**: Ethereum wallet address for receiving agent usage payments
* **Default Agent Pricing**: Set default price per use (USDC micro)
* View current usage statistics
* Set monthly limits (Pro/Enterprise plans)
* Configure billing alerts
### User Management
**Invite Team Members**:
Navigate to **Admin > Dashboard > Users** tab
Click the **"+ Invite User"** button
Type the email address of the person you want to invite
* **Member**: Can use agents, view dashboard
* **Support**: + Can view agent configurations
* **Admin**: + Can create/edit agents, manage settings
Click **"Send Invite"** - the user will receive an email invitation
**Change User Roles**:
* Find the user in the list
* Click the **role dropdown**
* Select the new role
**Remove Users**:
* Click the **trash icon** next to a user
* Confirm removal
***
## 🔍 Advanced Features
### Scheduled Tasks
**Automate agent execution** with schedules (cron jobs).
Go to **Admin > Schedules** (if available in your plan)
Click **"+ New Schedule"**
```yaml Schedule Configuration theme={null}
Name: Daily Report Generator
Prompt: Generate today's summary report
Schedule: Daily at 9:00 AM
Timezone: America/New_York
Session: Create new session each time
```
Click **"Run Now"** to test before saving
* **Once**: Run at a specific date/time
* **Daily**: Every day at X time
* **Weekly**: Specific days of the week
* **Monthly**: Specific day of the month
* **Cron**: Advanced cron expression (e.g., `0 9 * * 1-5` = weekdays at 9 AM)
### Workflow Builder (Coming Soon)
Visual drag-and-drop interface for building complex agent workflows.
**Features** (planned):
* Drag agents from a palette
* Draw connections between agents
* Set conditional logic (if/else branches)
* Test workflows in real-time
* Export/import workflow templates
***
## 💡 Tips for Effective Admin Management
* Begin with a **single LLM Agent** to handle basic tasks
* Test thoroughly before adding complexity
* Add Sequential/Parallel agents only when you need multi-step workflows
* Monitor performance before scaling up
**Naming Convention**:
* `orchestrator` - Main router
* `customer_support_agent` - Customer-facing
* `research_analyst` - Data analysis
* `email_sender` - Action agents
**Groups**:
* **Front-line**: Customer support, sales
* **Back-office**: Data processing, reporting
* **Specialized**: Legal, technical documentation
**Good**:
```
You are a customer support agent for Acme Corp.
Your responsibilities:
1. Answer questions about products A, B, C
2. Help troubleshoot common issues (login, billing)
3. Escalate to human support for complex cases
Important: Be friendly, concise, and professional.
Always verify user identity before sharing account info.
```
**Bad**:
```
You are a helpful assistant.
```
* Check **Dashboard metrics** weekly
* Review **Recent Activity** to see how agents are being used
* Update agent instructions based on user feedback
* Test edge cases regularly
* **Rotate API keys** every 90 days
* Use **user-level MCP credentials** for personal platforms (e.g., personal GitHub)
* Set **payment thresholds** for paid agents
* Review **user permissions** quarterly
* Enable **2FA** for all admin users (if available)
***
## 🆘 Common Issues and Solutions
**Possible Causes**:
* Agent is **inactive** (check the toggle)
* Agent is **not connected to Orchestrator** (check relationships)
* **Model API key** is missing (verify in SQLAdmin)
**Solution**:
1. Check agent status (green toggle)
2. Verify Orchestrator relationships
3. Test with a simple message
4. Check Dashboard > Recent Activity for errors
**Possible Causes**:
* Document **still indexing** (wait 1-2 minutes)
* **RAG tool not assigned** to agent
* Query doesn't match document content
**Solution**:
1. Check DataStore status (should be "Completed")
2. Verify agent has the RAG tool enabled
3. Test with a query that matches document keywords
4. Check Vertex AI Search console for indexing errors
**Possible Causes**:
* **Credentials expired or invalid**
* Platform not enabled for agent
* **Rate limiting** from external platform
**Solution**:
1. Re-authenticate the MCP platform
2. Check agent MCP platforms list
3. Verify API key permissions on the external platform
4. Check error logs in Dashboard > Recent Activity
**Possible Causes**:
* Browser cache issue
* Relationships not saved correctly
**Solution**:
1. Hard refresh the page (Ctrl+Shift+R or Cmd+Shift+R)
2. Switch to List view to verify relationships
3. Re-save the relationships if needed
***
## 📖 Next Steps
Connect external platforms via Model Context Protocol
Register Python functions as agent tools
Integrate Junis into your applications
***
## 🎓 Admin Interface Certification
Congratulations! You now know how to:
✅ Navigate the Admin dashboard and interpret metrics
✅ Create, edit, and delete agents with confidence
✅ Manage agent relationships and build workflows
✅ Upload RAG documents and assign to agents
✅ Configure MCP integrations (org-level and user-level)
✅ Generate and manage API keys securely
✅ Configure organization settings and team permissions
✅ Troubleshoot common issues independently
**Need More Help?** Check the [API Reference](/api-reference/authentication) for programmatic management or contact us at [contact@junis.ai](mailto:contact@junis.ai).
# Understanding the Agent System
Source: https://docs.junis.ai/getting-started/agent-system
Master the four agent types and build complex AI workflows
## Agent System Overview
The Junis agent system is a set of **four composable agent types** — LLM, Sequential, Parallel, and Loop — that you combine into AI teams without writing code. An orchestrator routes every request to the right agent, and each agent can call tools, MCP integrations, and sub-agents. This guide explains each type, when to use it, and how to combine them.
***
## What are the four agent types?
Uses language models to generate responses, make decisions, and execute tasks
Executes agents in order, passing results from one to the next
Runs multiple agents simultaneously, combining their results
Repeats agent execution until a condition is met or max iterations reached
***
## LLM Agent
### What It Does
LLM Agents are the **workhorses** of your AI system. They use language models (like Claude, gpt, or Gemini) to:
* Generate natural language responses
* Analyze and summarize text
* Make decisions based on input
* Call tools and APIs
* Query knowledge bases (RAG)
### When to Use
✅ Answering user questions
✅ Content generation (blogs, emails, summaries)
✅ Data analysis and insights
✅ Decision-making tasks
✅ Any task requiring language understanding
### Configuration Options
```yaml LLM Agent Settings theme={null}
name: My Agent
agent_type: LLM_AGENT
model: anthropic/claude-sonnet-5 # Or gpt, gemini, etc.
# Model Parameters
temperature: 0.7 # Creativity (0 = deterministic, 1 = creative)
max_output_tokens: 16000 # Response length limit
max_llm_calls: 3 # Max tool call iterations
# Instructions (System Prompt)
instruction: |
You are a helpful assistant that answers questions about our product.
Be concise, friendly, and accurate.
# Tools & Integrations
tools:
- search_database
- send_email
rag_tools:
- rag_product_docs
mcp_platforms:
- github
- firecrawl
# Advanced
streaming_enabled: true # Real-time response streaming
web_search_enabled: false # Enable web search capabilities
output_key: null # Extract specific field from response
# Extended Thinking (Claude models only)
enable_thinking: false # Enable extended thinking mode
thinking_budget_tokens: 2048 # Token budget for thinking (min: 1024)
reasoning_effort: none # "none", "low", "medium", "high"
```
### Example: Customer Support Agent
```yaml theme={null}
name: Support Agent
agent_type: LLM_AGENT
model: anthropic/claude-sonnet-5
temperature: 0.5
instruction: |
You are a customer support agent for Acme Corp.
Responsibilities:
1. Answer product questions using the knowledge base
2. Troubleshoot common issues
3. Escalate complex problems to human agents
Always be empathetic and solution-oriented.
rag_tools:
- rag_product_documentation
- rag_faq_database
```
**User**: How do I reset my password?
**Support Agent**: I'd be happy to help you reset your password! Here's how:
1. Go to the login page at acme.com/login
2. Click "Forgot Password?" below the login button
3. Enter your registered email address
4. Check your email for a reset link (arrives within 5 minutes)
5. Click the link and create a new password
If you don't receive the email, check your spam folder or contact us at [support@acme.com](mailto:support@acme.com).
Is there anything else I can help you with?
***
## Sequential Agent
### What It Does
Sequential Agents execute a **series of agents in order**, where each agent's output becomes the next agent's input. Think of it as a pipeline or assembly line.
```mermaid theme={null}
graph LR
A[Input] --> B[Agent 1]
B --> C[Agent 2]
C --> D[Agent 3]
D --> E[Final Output]
```
### When to Use
✅ Multi-step workflows (research → analyze → report)
✅ Data transformation pipelines
✅ Quality assurance (generate → review → refine)
✅ Complex decision trees
✅ When output of one task informs the next
### Configuration Options
```yaml Sequential Agent Settings theme={null}
name: My Sequential Workflow
agent_type: SEQUENTIAL_AGENT
# Sub-Agents (executed in order)
sub_agents:
- agent_id: researcher
order_index: 1
description: Gathers information from multiple sources
- agent_id: analyzer
order_index: 2
description: Analyzes and synthesizes research findings
- agent_id: writer
order_index: 3
description: Writes final report based on analysis
# Optional: Define output format
output_key: final_report
```
### Example: Content Creation Pipeline
```yaml theme={null}
name: Blog Post Generator
agent_type: SEQUENTIAL_AGENT
sub_agents:
# Step 1: Research
- agent: Topic Researcher
order: 1
role: Search web and knowledge base for relevant information
# Step 2: Outline
- agent: Content Outliner
order: 2
role: Create structured outline from research
# Step 3: Writing
- agent: Blog Writer
order: 3
role: Write engaging blog post following outline
# Step 4: SEO Optimization
- agent: SEO Optimizer
order: 4
role: Add keywords, meta description, headings
```
**Input**: "Write a blog post about AI safety"
**Step 1 (Topic Researcher)**:
* Searches web for latest AI safety news
* Queries knowledge base for expert opinions
* Output: Comprehensive research notes (2000 words)
**Step 2 (Content Outliner)**:
* Reads research notes from Step 1
* Creates structured outline with sections
* Output: 5-section outline with key points
**Step 3 (Blog Writer)**:
* Follows outline from Step 2
* Writes engaging 1500-word blog post
* Output: Draft blog post with intro, body, conclusion
**Step 4 (SEO Optimizer)**:
* Analyzes draft from Step 3
* Adds SEO metadata and formatting
* **Final Output**: Publication-ready blog post
Each step only sees the output from the previous step, keeping context focused and efficient.
### Best Practices
* Each sub-agent should have ONE clear responsibility
* Don't try to do too much in a single step
* Break complex tasks into 3-5 discrete steps
**Bad Example**: "Research, analyze, and write report" (one agent)
**Good Example**: Research Agent → Analysis Agent → Writing Agent (three agents)
When you need specific data from a step, use `output_key` to extract it:
```yaml theme={null}
- agent: Data Extractor
order: 1
output_key: extracted_data # Only pass this field to next step
```
This prevents information overload in later steps.
Insert validation agents to ensure quality:
```yaml theme={null}
sub_agents:
- agent: Content Generator
order: 1
- agent: Quality Checker # ← Validates output
order: 2
- agent: Formatter
order: 3
```
***
## Parallel Agent
### What It Does
Parallel Agents run **multiple agents simultaneously** and combine their results. Perfect for gathering information from multiple sources or performing independent tasks concurrently.
```mermaid theme={null}
graph TD
A[Input] --> B[Agent 1]
A --> C[Agent 2]
A --> D[Agent 3]
B --> E[Combined Output]
C --> E
D --> E
```
### When to Use
✅ Data collection from multiple sources
✅ Independent analyses that can run concurrently
✅ Speeding up workflows with parallelization
✅ Comparing different approaches
✅ Aggregating diverse perspectives
### Configuration Options
```yaml Parallel Agent Settings theme={null}
name: My Parallel Workflow
agent_type: PARALLEL_AGENT
# Sub-Agents (executed simultaneously)
sub_agents:
- agent_id: source_a_collector
description: Fetches data from Source A
- agent_id: source_b_collector
description: Fetches data from Source B
- agent_id: source_c_collector
description: Fetches data from Source C
# Results are automatically combined
# Order doesn't matter for parallel execution
```
### Example: Multi-Source Data Aggregator
```yaml theme={null}
name: Market Research Aggregator
agent_type: PARALLEL_AGENT
sub_agents:
# All run simultaneously
- agent: Competitor Analysis Agent
role: Analyze competitor websites and pricing
- agent: Social Media Insights Agent
role: Gather sentiment from Twitter, Reddit, LinkedIn
- agent: Industry News Agent
role: Summarize latest industry news and trends
- agent: Customer Feedback Agent
role: Analyze recent customer reviews and support tickets
```
```
t=0s: All agents start simultaneously
├─ Competitor Analysis (15s)
├─ Social Media Insights (20s)
├─ Industry News (10s)
└─ Customer Feedback (18s)
t=20s: All agents complete
Results are combined into single report
Total Time: 20 seconds (vs 63 seconds if sequential)
```
Parallel execution requires sufficient API rate limits and resources. Monitor usage to avoid throttling.
The Parallel Agent automatically combines results:
```json theme={null}
{
"competitor_analysis": "Our pricing is 15% lower than competitors...",
"social_sentiment": "Positive sentiment at 68%, trending topics include...",
"industry_news": "New regulations announced today...",
"customer_feedback": "Top feature requests: mobile app, API access..."
}
```
You can add a **Compiler Agent** after the Parallel Agent to synthesize results into a cohesive narrative.
### Best Practices
Parallel agents should NOT depend on each other's results:
**Bad Example**: Agent A calculates total, Agent B calculates percentage of total
**Good Example**: Agent A analyzes Dataset 1, Agent B analyzes Dataset 2 (independent)
If one parallel agent fails, others should still complete:
```yaml theme={null}
# Enable fault tolerance
parallel_agent:
continue_on_error: true # Don't stop if one agent fails
timeout: 30 # Max wait time per agent (seconds)
```
After parallel execution, add a Sequential step to combine results:
```mermaid theme={null}
graph TD
A[Input] --> B[Parallel Agent]
B --> C[Agent 1]
B --> D[Agent 2]
B --> E[Agent 3]
C --> F[Compiler Agent]
D --> F
E --> F
F --> G[Unified Output]
```
The Compiler Agent synthesizes parallel results into a coherent response.
***
## Loop Agent
### What It Does
Loop Agents **repeat execution** until a condition is met or maximum iterations reached. Useful for iterative refinement, retry logic, and self-correcting workflows.
```mermaid theme={null}
graph TD
A[Input] --> B[Agent Execution]
B --> C{Condition Met?}
C -->|No| B
C -->|Yes| D[Final Output]
C -->|Max Iterations| E[Exit with Error]
```
### When to Use
✅ Iterative refinement (generate → evaluate → improve)
✅ Retry logic with exponential backoff
✅ Quality assurance loops
✅ Self-correcting systems
✅ Tasks requiring multiple attempts
### Configuration Options
```yaml Loop Agent Settings theme={null}
name: My Loop Workflow
agent_type: LOOP_AGENT
max_llm_calls: 5 # Maximum iterations (prevents infinite loops)
# Sub-Agent to Loop
sub_agents:
- agent_id: iterative_improver
description: Improves output each iteration
# Exit Conditions (implicit)
# - Condition evaluates to true
# - Max iterations reached
# - Agent returns success signal
```
### Example: Code Review and Refinement
```yaml theme={null}
name: Code Quality Loop
agent_type: LOOP_AGENT
max_llm_calls: 3
sub_agents:
- agent: Code Generator
role: Generate Python function based on requirements
- agent: Code Reviewer
role: |
Review code for:
- Correctness
- Edge cases
- Best practices
- Security issues
If issues found, provide specific feedback for next iteration.
If code is acceptable, respond with "APPROVED".
```
**Iteration 1**:
* Generator creates initial code
* Reviewer finds bug: "Missing null check on input"
* Loop continues
**Iteration 2**:
* Generator adds null check
* Reviewer finds issue: "Function doesn't handle empty lists"
* Loop continues
**Iteration 3**:
* Generator handles empty lists
* Reviewer responds: "APPROVED - Code meets quality standards"
* **Loop exits** with final code
Code quality improved through automated iterative review!
If max iterations (3) reached without approval:
```json theme={null}
{
"status": "max_iterations_reached",
"iterations": 3,
"final_output": "Latest code version",
"reviewer_feedback": "Still has issues with error handling",
"action": "Escalate to human review"
}
```
Always set `max_llm_calls` to prevent infinite loops. Recommended: 3-5 iterations.
### Best Practices
The sub-agent should explicitly signal when to exit:
```yaml theme={null}
instruction: |
Review the output. If quality is acceptable, respond with:
"EXIT: Quality standards met"
Otherwise, provide specific improvement suggestions.
```
* **2-3 iterations**: Quick refinement tasks
* **3-5 iterations**: Moderate complexity
* **5-10 iterations**: Complex optimization (rare)
Higher values increase cost and latency. Most tasks converge in 2-3 iterations.
Pass iteration count to the agent for context:
```yaml theme={null}
instruction: |
This is iteration {{iteration_count}} of {{max_iterations}}.
Previous feedback: {{previous_feedback}}
Improve the output based on feedback.
```
Handle max iterations gracefully:
```yaml theme={null}
# After loop exits
if iterations >= max_llm_calls:
escalate_to_human_review()
log_warning("Loop did not converge")
return best_attempt
```
***
## Combining Agent Types
The real power of Junis comes from **combining different agent types** to build sophisticated workflows.
### Example: Full Blog Post System
```mermaid theme={null}
graph TD
A[User Request] --> B[Orchestrator]
B --> C[Sequential: Content Pipeline]
C --> D[Parallel: Research Team]
D --> E[Web Researcher]
D --> F[Doc Researcher]
D --> G[Expert Interview Agent]
E --> H[Compiler]
F --> H
G --> H
H --> I[Outline Agent]
I --> J[Loop: Writer + Reviewer]
J --> K{Approved?}
K -->|No| J
K -->|Yes| L[SEO Optimizer]
L --> M[Final Blog Post]
```
User says: "Write a blog post about quantum computing"
Orchestrator recognizes this as a content creation task and routes to **Content Pipeline** (Sequential Agent).
Content Pipeline executes three stages:
1. Research (Parallel Agent)
2. Writing (Loop Agent)
3. Optimization (LLM Agent)
Research Team runs three agents simultaneously:
* **Web Researcher**: Latest quantum computing news
* **Doc Researcher**: Academic papers from knowledge base
* **Expert Interview Agent**: Queries interview transcripts
Results feed into **Compiler Agent** that synthesizes a research brief.
**Writer + Reviewer Loop**:
* Writer drafts blog post from research
* Reviewer checks quality, accuracy, readability
* Loop continues until approved (max 3 iterations)
SEO Optimizer adds:
* Meta description
* Keywords
* Heading structure
* Internal links
**Output**: Publication-ready blog post delivered in \~45 seconds.
***
## Which agent type should you use?
Not sure which agent type to use? Follow this decision tree:
```mermaid theme={null}
graph TD
A[Start] --> B{Multiple steps?}
B -->|Yes| C{Steps independent?}
B -->|No| D{Need iteration?}
C -->|Yes| E[Parallel Agent]
C -->|No| F[Sequential Agent]
D -->|Yes| G[Loop Agent]
D -->|No| H[LLM Agent]
```
| Scenario | Agent Type | Reason |
| - | - | - |
| Answer a question | LLM Agent | Single-step task |
| Research → Analyze → Report | Sequential | Multi-step, order matters |
| Check 3 databases simultaneously | Parallel | Independent, concurrent |
| Generate → Review → Improve | Loop | Iterative refinement |
| Route to appropriate handler | Orchestrator | Dynamic dispatching |
**Start Simple**: Begin with LLM Agents
**Add Complexity As Needed**:
1. **LLM Agent** (Day 1): Get basic functionality working
2. **Sequential Agent** (Week 1): Add multi-step workflows
3. **Parallel Agent** (Week 2): Optimize with parallelization
4. **Loop Agent** (Week 3): Add self-correction and quality loops
80% of use cases can be solved with LLM + Sequential Agents. Add Parallel and Loop only when needed.
***
## Common Patterns
**Use Case**: Generate insights from data
```yaml theme={null}
Sequential Agent:
- Parallel Research: Gather data from multiple sources
- LLM Analysis: Identify trends and patterns
- LLM Report Writer: Create executive summary
```
**Use Case**: Quality assurance
```yaml theme={null}
Loop Agent:
- LLM Generator: Create output
- LLM Validator: Check against criteria
- If invalid: Provide feedback and retry
- If valid: Exit loop
```
**Use Case**: Smart routing
```yaml theme={null}
Sequential Agent:
- LLM Triage: Classify request
- Orchestrator: Route to specialist agent
- Specialist LLM: Handle request
```
**Use Case**: Evaluate multiple approaches
```yaml theme={null}
Parallel Agent:
- LLM Approach A: Conservative solution
- LLM Approach B: Creative solution
- LLM Approach C: Balanced solution
Then:
- LLM Evaluator: Pick best approach
```
***
## Performance Considerations
* **LLM Agent**: 2-10 seconds
* **Sequential Agent**: Sum of sub-agents
* **Parallel Agent**: Max of sub-agents
* **Loop Agent**: Iterations × agent time
* **LLM Agent**: 1 LLM call
* **Sequential Agent**: N LLM calls
* **Parallel Agent**: N LLM calls (concurrent)
* **Loop Agent**: Up to max\_iterations × calls
### Optimization Tips
* **Development**: Gemini Flash (fast, cheap)
* **Production**: Claude Sonnet (balanced)
* **Complex Tasks Only**: Claude Opus / GPT-5.5
Switching from Opus to a faster model like Gemini 3.7 Flash can substantially reduce latency and cut per-token costs by roughly two-thirds.
```yaml theme={null}
streaming_enabled: true # Show tokens as they arrive
```
Improves perceived performance even if total time is the same.
* **2-3 parallel agents**: Optimal for most cases
* **4-5 parallel agents**: Acceptable with monitoring
* **6+ parallel agents**: Risk rate limiting and high costs
Consider Sequential + Parallel hybrid instead of large Parallel groups.
```yaml theme={null}
Loop Agent:
max_llm_calls: 3 # Rarely need more than 3 iterations
```
Most loops converge in 2-3 iterations. Higher values rarely improve output quality.
***
## Next Steps
Connect agents to APIs and external services
Integrate GitHub, Firecrawl, and custom services
Let agents query your documents
Let one squad's orchestrator ask another squad and relay the answer
Give every agent a shared second brain that persists across conversations
***
**Questions?** Contact us at [contact@junis.ai](mailto:contact@junis.ai) to discuss agent architectures and get expert guidance.
# Introduction to Junis
Source: https://docs.junis.ai/getting-started/introduction
Build powerful multi-agent AI systems without writing code
## Welcome to Junis
Junis is an AI workforce platform where organizations build, orchestrate, and deploy teams of AI agents without writing code. Each team combines 4 agent types (LLM, Sequential, Parallel, Loop) with MCP integrations, 200+ Composio OAuth apps, a shared organization wiki, persistent memory, an embeddable chat widget, and an OpenAI-compatible API.
***
## What is Junis?
Junis transforms how you build with AI by providing:
Orchestrate multiple AI agents that collaborate to solve complex tasks
Build sophisticated AI workflows through an intuitive visual interface
Multi-tenant architecture with organization-level isolation and security
Connect to external tools, databases, and APIs seamlessly
***
## What are Junis's key features?
### 🤖 Dynamic Agent System
Create four types of agents to handle any workflow:
| Agent Type | Purpose | Use Case |
| - | - | - |
| **LLM Agent** | Executes AI tasks using language models | Chat, analysis, content generation |
| **Sequential Agent** | Runs agents in order, step by step | Multi-stage workflows (research → analyze → report) |
| **Parallel Agent** | Runs multiple agents simultaneously | Data collection from multiple sources |
| **Loop Agent** | Repeats agent execution until condition met | Iterative refinement, quality checks |
### 🔧 Powerful Integrations
Connect to external services including GitHub and Firecrawl via Model Context Protocol. Add custom MCP servers for your internal tools.
Upload documents and create searchable knowledge bases. Your agents can query PDFs, Word docs, spreadsheets, and more.
Register Python functions as agent tools. Call APIs, process data, or integrate with any service.
Enable real-time web search for agents to access the latest information beyond their training data.
### 🧠 Second Brain & Collaboration
A shared second brain every agent reads and writes — say "save this to the wiki" once and your whole AI team knows it
One squad's orchestrator asks another squad a question and relays the answer — chain depth 3, loops blocked
Put your AI team on any website with a single iframe — visitors chat without logging in
OpenAI-compatible API plus user provisioning to build your own product on top of Junis
### 💬 Seamless Chat Experience
* **Real-time Streaming**: See agent responses as they're generated
* **Multi-turn Conversations**: Maintain context across multiple messages
* **File Attachments**: Upload images, documents, and files for analysis
* **Voice Input**: Record and transcribe voice messages
### 💳 Flexible Billing
Monthly plans with included credits for AI agent usage
Pay-per-use credits for flexible consumption tracking
Manage your subscription and monitor usage from the **Subscription** page. Credits are automatically deducted based on agent activity.
### 🔐 Enterprise Security
Each organization has its own agents, data, and configurations
Fine-grained permissions: Super Admin, Admin, Support, Member
Generate and manage API keys for external integrations
Track all actions and changes for compliance
***
## How does Junis work?
```mermaid theme={null}
graph LR
A[User Message] --> B[Orchestrator]
B --> C{Agent Selection}
C --> D[LLM Agent]
C --> E[Sequential Agent]
C --> F[Parallel Agent]
D --> G[Tools & MCP]
E --> G
F --> G
G --> H[Response]
H --> A
```
1. **User sends a message** through the chat interface or API
2. **Orchestrator analyzes** the request and selects the appropriate agent
3. **Agent executes** using LLMs, tools, RAG, or MCP integrations
4. **Response streams** back to the user in real-time
5. **Context persists** across the conversation session
***
## Who Uses Junis?
Build customer support chatbots, onboarding assistants, and internal tools without engineering resources.
Automate data analysis, generate reports, and query multiple databases simultaneously.
Generate blog posts, social media content, and marketing copy with multi-agent orchestrations.
Integrate AI capabilities into your apps using OpenAI-compatible APIs and webhooks.
***
## How does Junis compare to other platforms?
| Feature | Junis | LangChain | AutoGPT |
| - | - | - | - |
| **No-Code Interface** | ✅ Visual workflow builder | ❌ Code-based | ❌ Code-based |
| **Multi-Agent System** | ✅ 4 agent types | ✅ Limited | ✅ Limited |
| **Real-time Streaming** | ✅ WebSocket + SSE | ⚠️ Basic | ❌ Polling |
| **Multi-Tenant** | ✅ Organization isolation | ❌ Self-hosted only | ❌ Single user |
| **MCP Integration** | ✅ GitHub, Firecrawl + Custom | ❌ Manual setup | ❌ Not supported |
| **API Compatibility** | ✅ OpenAI-compatible | ❌ Custom | ❌ Custom |
| **Deployment** | ✅ Cloud + On-premise | ⚠️ Self-hosted | ⚠️ Self-hosted |
***
## Quick Start
Ready to build your first AI agent?
Create your first chatbot in 5 minutes
Learn about the four agent types
Integrate Junis into your applications
***
## Support & Community
Comprehensive guides and tutorials
Contact our team for assistance
***
**New to AI agents?** Don't worry! Our [Quick Start Guide](/getting-started/quickstart) walks you through creating your first agent step-by-step, with no prior knowledge required.
# Quick Start Guide
Source: https://docs.junis.ai/getting-started/quickstart
Create your first AI agent in 5 minutes
## What will you build?
Junis is an AI workforce platform where you create, orchestrate, and deploy teams of AI agents through a web interface — no code required. In this tutorial, you sign in, configure your first LLM agent, connect it to your organization's orchestrator, and test it in a live chat, all in about 5 minutes.
In this tutorial, you'll create a **Customer Support Agent** that can:
* Answer common questions about your product
* Route complex issues to the right team
* Maintain context across conversations
**Time Required**: 5 minutes
**Prerequisites**: Google account (for login)
***
## Step 1: Sign In
Navigate to [https://junis.ai](https://junis.ai) and click **"Sign in with Google"**
Grant Junis permission to access your Google account. We only use this for authentication.
Junis never accesses your Gmail, Drive, or other Google services without explicit permission.
You'll be prompted to:
* Create or join an AI Team
* Set your display name
* Choose your role
For this tutorial, select **"Create New AI Team"** and name it anything you like (e.g., "Acme Support").
***
## Step 2: Create Your First Agent
After logging in, you'll see the main dashboard. Click **"Team Management"** dropdown in the Navigation Bar, then select **"Agent Orchestration"**.
In the Orchestration tab, you'll see a pre-configured **Orchestrator** agent (don't modify this yet).
The Orchestrator is the "brain" that routes user messages to the right agents. Every AI Team has one.
Click the **"New Agent"** button.
***
## Step 3: Configure Your Agent
Fill in the agent creation form:
```yaml Basic Info theme={null}
Name: Customer Support Agent
Description: Helps customers with common questions and routes complex issues
Agent Type: LLM Agent
Status: Active ✅
```
```yaml Model Settings theme={null}
Model: Claude Sonnet 5 (default)
Temperature: 0.7
Max Output Tokens: 1000
```
```yaml Instructions theme={null}
You are a helpful customer support agent for Acme Corp.
Your responsibilities:
1. Answer common questions about our product
2. Help troubleshoot basic issues
3. Route complex technical problems to the engineering team
4. Be friendly, professional, and concise
Our product: Acme Widgets - cloud-based widget management software
Common questions:
- Pricing: $29/month for Pro, $99/month for Enterprise
- Free trial: 14 days, no credit card required
- Support hours: 9 AM - 6 PM EST, Monday-Friday
- Contact: support@acme.com or live chat
If you cannot help, politely ask the user to contact support@acme.com.
```
* **Agent Type**: LLM Agent uses a language model to generate responses
* **Model**: Claude Sonnet 5 is fast and cost-effective for most tasks
* **Temperature**: 0.7 balances creativity and consistency (0 = deterministic, 1 = creative)
* **Max Output Tokens**: Limits response length to control costs
* **Instructions**: The "system prompt" that defines the agent's behavior
Click **"Save"** to create your agent.
***
## Step 4: Connect Agent to Orchestrator
Go back to the Agents list and click **"Edit"** on the **Orchestrator** agent.
Scroll to the **"Sub-Agents"** section and click **"+ Add Sub-Agent"**.
Select **"Customer Support Agent"** from the dropdown and set:
* **Order**: 1 (execution priority)
* **Description**: "Handles customer support questions"
In the **Instructions** field, add:
```yaml theme={null}
You are the Orchestrator for Acme Corp.
Route user messages to the appropriate agent:
- Customer support questions → Customer Support Agent
- General conversation → Respond directly
Always be helpful and professional.
```
Click **"Save"**.
***
## Step 5: Test Your Agent
Click **"Chat"** in the sidebar, then click **"+ New Chat"**.
Try these example messages:
```text Example 1 theme={null}
How much does Acme Widgets cost?
```
```text Example 2 theme={null}
What's your free trial policy?
```
```text Example 3 theme={null}
I'm having trouble logging in to my account
```
Watch the **Agent Pipeline Viewer** at the bottom of the chat:
1. **Orchestrator** receives your message
2. Routes to **Customer Support Agent**
3. Response streams back in real-time
The agent should correctly answer pricing and trial questions based on the instructions you provided.
***
## Congratulations! 🎉
You've created your first AI agent! Here's what you learned:
✅ How to create and configure an LLM Agent
✅ How to write effective agent instructions
✅ How to connect agents to the Orchestrator
✅ How to test agents in the chat interface
***
## Next Steps
Connect to APIs, databases, and external services
Chain multiple agents together
Integrate GitHub, Firecrawl, and custom MCP servers
***
## Tips for Better Agents
* Be specific about the agent's role and responsibilities
* Provide examples of expected inputs and outputs
* Include edge cases (e.g., "If you cannot help, say X")
* Use bullet points for readability
* Begin with basic functionality
* Test thoroughly with real user messages
* Add complexity (tools, RAG, sub-agents) gradually
* Monitor agent performance and refine instructions
* **Gemini 3.7 Flash**: Fast, good for simple tasks ($1.95 input / $9.75 output per 1M tokens)
* **Claude Sonnet 5**: Balanced speed and quality — the platform default ($3.50 input / $17.30 output per 1M tokens)
* **Claude Opus 4.x**: Most capable, best reasoning ($5.75 input / $28.75 output per 1M tokens)
* **GPT-5.5**: Strong for structured outputs and function calling ($3.13 input / $18.75 output per 1M tokens)
Try these scenarios:
* Ambiguous questions
* Out-of-scope requests
* Multi-turn conversations
* Requests in different languages
* Adversarial inputs (jailbreaks, prompt injections)
***
## Common Issues
**Possible causes:**
* Agent is inactive (check Status toggle)
* Orchestrator didn't route to your agent (check Agent Pipeline Viewer)
* API key missing for the selected model
**Solution:**
* Ensure agent Status is **Active** (green toggle)
* Check Orchestrator instructions include your agent
* Verify API keys in Settings > Organization
**Possible causes:**
* Instructions are too vague
* Temperature is too high (> 0.9)
* Agent doesn't have enough context
**Solution:**
* Rewrite instructions with specific examples
* Lower temperature to 0.5-0.7 for consistency
* Add relevant information to the system prompt
**Possible causes:**
* Using a slower, heavier model (e.g., Claude Opus, GPT-5.4 Pro)
* Agent is calling multiple tools/sub-agents
* Network latency
**Solution:**
* Switch to Gemini Flash or Sonnet for faster responses
* Optimize tool calls (reduce number of API requests)
* Use streaming for better perceived performance
***
## Need Help?
Contact our team directly
Integrate with OpenAI-compatible API
**Pro Tip**: Explore **Public Agents** in Agent Orchestration > New Agent > Public Agent to discover pre-built templates you can clone and customize for your AI Team.
# Credit System Guide
Source: https://docs.junis.ai/guides/billing/credits
Understanding how credits work in Junis
## Overview
Credits are the currency for AI agent usage in Junis. Every AI model interaction consumes credits based on the model's complexity and token usage.
**1 Credit ≈ \$1 USD** equivalent in AI model usage. Actual consumption varies by model.
***
## How Credits Work
### Credit Consumption
When you send a message or run an agent workflow:
1. **Input Processing**: Credits are calculated based on input tokens
2. **Model Execution**: The AI model processes your request
3. **Output Generation**: Additional credits are consumed for generated content
4. **Credit Deduction**: Total usage is deducted from your balance
### Cost Factors
| Factor | Impact |
| - | - |
| **Model Selection** | Premium models (Claude Opus, GPT-5.5) cost more than efficient models (Gemini Flash) |
| **Input Length** | Longer prompts and context consume more credits |
| **Output Length** | Longer responses cost more |
| **Tool Usage** | External tool calls may add to credit consumption |
***
## Checking Your Balance
### From the Dashboard
Your current credit balance is displayed in the top navigation bar after logging in.
### From the Subscription Page
For detailed balance information:
1. Go to `/subscription`
2. View your **Credit Balance** card
3. See total credits and recent usage
### From the Usage Page
For historical usage data:
1. Go to `/usage`
2. View credit consumption over time
3. See breakdown by agent and model
***
## Credit Sources
### Subscription Credits
Monthly subscriptions include credits that refresh each billing cycle:
| Plan | Monthly Credits |
| - | - |
| Basic | 20 credits |
| Pro | 195 credits |
Subscription credits do not roll over. Unused credits expire at the end of each billing period.
### One-Time Purchases
Credits purchased individually:
| Package | Credits |
| - | - |
| Token 30 | 30 credits |
| Token 60 | 60 credits |
| Token 95 | 95 credits |
One-time purchase credits **never expire**. Use them whenever you need extra capacity.
***
## AI Model Pricing
Different AI models have different credit costs. Generally:
**Gemini Flash**
Fast, affordable for simple tasks
**Claude Sonnet**
Best value for most use cases
**Claude Opus**
Maximum capability for complex tasks
**Pro Tip**: Use Claude Sonnet for most tasks. It offers an excellent balance of quality and cost.
***
## Optimizing Credit Usage
### Best Practices
* Use **Gemini Flash** for simple Q\&A and classification
* Use **Sonnet** for general tasks and content generation
* Reserve **Opus** for complex reasoning and analysis
* Be clear and concise in your instructions
* Avoid unnecessary context
* Use system prompts to set agent behavior once
* Design efficient Sequential/Parallel flows
* Avoid redundant agent calls
* Cache results when possible (via agent instructions)
* Check the Usage page weekly
* Identify high-consumption agents
* Adjust configurations as needed
***
## Low Balance Alerts
When your credit balance runs low:
1. **Warning**: Yellow indicator when below 10% of monthly allocation
2. **Critical**: Red indicator when below 5%
3. **Depleted**: Agent executions may be limited
Enable email notifications in Settings to receive low balance alerts.
***
## Credit FAQ
Agent executions may be paused or limited. You can purchase additional credits at any time or wait for your next subscription renewal.
Subscription credits are non-refundable. One-time purchases may be eligible for refund within 7 days if unused. Contact support for assistance.
No, credits are tied to the user account. However, your credits work across all organizations you belong to.
Credit balance updates in real-time. There may be a brief delay (1-2 seconds) during active agent execution.
***
## Related Guides
Manage your subscription plan
Learn about agent types and optimization
# Subscription & Payment Guide
Source: https://docs.junis.ai/guides/billing/subscription
Manage your Junis subscription and payment options
## Overview
Junis offers flexible subscription plans to power your AI agent workflows. Choose a plan that fits your team's needs and scale as you grow.
All payments are processed securely through Lemon Squeezy. Your payment information is never stored on Junis servers.
***
## Subscription Plans
**\$20/month** (₩33,300)
* 20 AI credits monthly
* Access to all AI models
* Up to 3 AI teams
* Team member management
* API development access
* Unlimited chat sessions
* Basic support
**\$195/month** (₩299,000)
* 195 AI credits monthly
* Access to all AI models
* Unlimited AI teams
* Advanced team controls
* Priority API access
* Unlimited everything
* Priority support
* **10% platform savings**
**Pro Plan Value**: The Pro plan offers a 10% discount compared to purchasing credits individually, plus priority support and unlimited teams.
***
## Getting Started
### Step 1: Navigate to Subscription Page
From any page in Junis, click your profile icon and select **"Subscription"**, or go directly to `/subscription`.
### Step 2: Choose Your Plan
Review the available plans and click **"Subscribe"** on your preferred option.
### Step 3: Complete Payment
You'll be redirected to a secure Lemon Squeezy checkout page. Enter your payment details and complete the purchase.
**Immediate Access**: Your credits are available immediately after successful payment.
***
## Managing Your Subscription
### View Current Plan
The Subscription page displays:
* Your current plan (Basic, Pro, or None)
* Subscription status (Active, Cancelled, Past Due)
* Next renewal date
* Available credits
### Cancel Subscription
Navigate to `/subscription` or click Subscription in your profile menu.
Find the **"Cancel Subscription"** button in your current plan section.
Your subscription will remain active until the end of the current billing period.
Unused credits do not roll over after cancellation. Use your credits before your plan ends.
### Resume Cancelled Subscription
If you've cancelled but your subscription hasn't expired yet, you can resume:
1. Go to the Subscription page
2. Click **"Resume Subscription"**
3. Your subscription will continue with the next billing cycle
### Upgrade or Downgrade
To change your plan:
1. Cancel your current subscription
2. Wait for the billing period to end
3. Subscribe to your desired new plan
**Coming Soon**: In-app plan switching with prorated billing is planned for a future release.
***
## One-Time Credit Purchases
Need more credits without a subscription? Purchase credits individually:
| Package | Price | Credits | Best For |
| - | - | - | - |
| Token 30 | \$30 (₩49,900) | 30 credits | Light usage |
| Token 60 | \$60 (₩99,900) | 60 credits | Most popular |
| Token 95 | \$95 (₩155,500) | 95 credits | Heavy usage |
One-time purchases are perfect for trying Junis before subscribing or for occasional extra capacity.
***
## Billing Cycle
* **Monthly Plans**: Billed on the same day each month
* **Credits**: Added to your balance immediately upon renewal
* **Unused Credits**: Do not roll over to the next month (subscription plans)
* **One-Time Purchases**: Credits never expire
***
## Payment Methods
Junis accepts:
* Credit/Debit Cards (Visa, Mastercard, American Express)
* PayPal (where available)
* Local payment methods (varies by region)
***
## Subscription Status Reference
| Status | Description |
| - | - |
| `active` | Subscription is current and active |
| `on_trial` | Trial period active, not yet charged |
| `paused` | Subscription temporarily paused |
| `past_due` | Payment failed, action required |
| `unpaid` | Multiple payment failures, service may be limited |
| `cancelled` | Cancelled but still active until period end |
| `expired` | Subscription has ended |
***
## Troubleshooting
**Possible causes:**
* Insufficient funds
* Card expired or declined
* Bank security block
**Solution:**
* Check your payment method is valid
* Contact your bank if the issue persists
* Try a different payment method
**Possible causes:**
* Payment processing delay
* Page not refreshed
**Solution:**
* Wait 1-2 minutes and refresh the page
* Check your email for payment confirmation
* Contact support if credits don't appear within 10 minutes
**Possible causes:**
* Already cancelled (pending expiration)
* Technical issue
**Solution:**
* Check subscription status shows "Active"
* Try logging out and back in
* Contact support for assistance
***
## Support
Need help with billing?
Contact our billing team
Learn about credit usage
# AI Content Generation Guide
Source: https://docs.junis.ai/guides/content/generation
Generate images, videos, audio, and 3D models using AI
## Overview
Junis integrates with Replicate AI to provide powerful content generation capabilities. Create images, videos, audio, and 3D models through natural language prompts.
Content generation consumes credits based on the model used and processing time. Check your credit balance before generating.
***
## Supported Content Types
SDXL, Flux, and more
AI video generation
Music and sound effects
GLB format models
***
## Generating Content
### Through Chat
Ask your AI agent to generate content using natural language:
```
"Generate an image of a sunset over mountains"
"Create a 10-second video of ocean waves"
"Make background music for a podcast intro"
```
### Through Team Content Page
1. Navigate to **Team > Content**
2. View all generated content for your organization
3. Filter by type, status, or date
4. Retry failed generations or delete content
***
## Content Status
| Status | Description |
| - | - |
| `pending` | Waiting to be processed |
| `processing` | Generation in progress |
| `completed` | Successfully generated |
| `failed` | Generation failed (can be retried) |
Processing time varies by content type. Images typically complete in 10-30 seconds, while videos may take several minutes.
***
## Managing Content
### Viewing Content
From the Team Content page, you can:
* **Preview** generated content directly in the browser
* **Download** content to your device
* **View details** including prompt, model used, and processing time
### Retrying Failed Content
If content generation fails:
1. Find the failed item in the content list
2. Click **Retry**
3. The system will attempt to regenerate the content
### Deleting Content
To remove content:
1. Select the content item
2. Click **Delete**
3. Confirm the deletion
Deleted content cannot be recovered. Storage space is freed immediately.
***
## Technical Details
This section is for administrators and developers who need to understand how the `generate_content` tool works internally.
### Tool Parameters
The `gen_content` tool accepts the following parameters:
| Parameter | Type | Required | Description |
| - | - | - | - |
| `version` | string | **Yes** | Replicate model identifier (e.g., `stability-ai/sdxl`) |
| `input_values` | object | **Yes** | Model-specific input parameters (must include `prompt` for most models) |
| `prefer_wait` | boolean | No | Wait for completion inline (default: `false`) |
### Generation Modes
**`prefer_wait=false`** - Queue and return immediately
The task is queued in the background via Celery workers. This is the default mode when generating content through chat.
**Return Structure:**
```json theme={null}
{
"success": true,
"generation_id": "550e8400-e29b-41d4-a716-446655440000",
"message": "Content generation started. Results: https://junis.ai/content/550e8400...",
"model": "stability-ai/sdxl"
}
```
**`prefer_wait=true`** - Wait for completion (polling)
The tool polls every 10 seconds until completion, with a maximum timeout of 15 minutes. Useful for integrations that need immediate results.
**Return Structure (on success):**
```json theme={null}
{
"success": true,
"generation_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"output_url": "https://replicate.delivery/...",
"storage_url": "https://.blob.core.windows.net//generation/...",
"content_type": "image",
"processing_time_ms": 12500,
"model": "stability-ai/sdxl"
}
```
### Popular Models
| Category | Model | Example Use |
| - | - | - |
| **Image** | `stability-ai/sdxl` | High-quality images |
| **Image** | `black-forest-labs/flux-schnell` | Fast image generation |
| **Video** | `stability-ai/stable-video-diffusion` | Short video clips |
| **Audio** | `meta/musicgen` | Music generation |
| **3D** | `abiruyt/text-to-3d` | 3D model generation (GLB) |
### Credit Cost Lookup
Credit costs are determined by a priority-based lookup system:
1. **Exact Match**: `stability-ai/sdxl` → matches pattern `stability-ai/sdxl`
2. **Provider Wildcard**: `stability-ai/sdxl` → matches pattern `stability-ai/*`
3. **Global Wildcard**: Any model → matches pattern `*`
4. **Default**: If no pattern matches, defaults to 1 credit
Credit costs are managed by administrators in the System Admin panel (ReplicateModelCredit table).
***
## Credit Costs
Content generation costs vary by type and complexity:
| Content Type | Typical Cost |
| - | - |
| Image (standard) | 0.01-0.05 credits |
| Image (high-res) | 0.05-0.15 credits |
| Video (short) | 0.10-0.50 credits |
| Audio | 0.05-0.20 credits |
| 3D Model | 0.20-0.50 credits |
Check your credit balance in the Subscription page before generating large amounts of content.
***
## Best Practices
Be specific about what you want:
* Include style, mood, and subject details
* Specify dimensions or duration if needed
* Mention any constraints or requirements
Test with smaller/faster generations before creating final content:
* Use lower resolution for image tests
* Create shorter video clips first
* Iterate on prompts before final generation
Keep track of content generation:
* Check the Content page regularly
* Review credit consumption
* Clean up unused content
***
## Troubleshooting
**Possible causes:**
* High system load
* Complex content request
* Large output size
**Solution:**
* Wait for processing to complete (timeout: 15 minutes)
* Check content status in Team > Content
* Retry if the request times out
**Possible causes:**
* Invalid prompt
* Model temporarily unavailable
* Insufficient credits
**Solution:**
* Check error message in content details
* Modify your prompt and retry
* Verify credit balance
**Possible causes:**
* Still processing
* Page needs refresh
**Solution:**
* Refresh the page
* Check the processing status
* Wait a few moments and check again
***
## Related Guides
Understand credit consumption
Learn about AI agents
# Junis Magic
Source: https://docs.junis.ai/guides/junis-magic
Manage your entire Junis platform from Claude Desktop, Cursor, or any MCP-compatible client
## What is Junis Magic?
**Junis Magic** is Junis's official MCP (Model Context Protocol) server that gives AI assistants complete access to manage your Junis platform. Instead of navigating the web dashboard, you can build, configure, and orchestrate AI agent teams directly from tools like **Claude Desktop**, **Cursor**, or any MCP-compatible client.
> Build and manage entire AI teams with natural language — no code, no dashboard clicking.
**One-Line Setup**: Just add the Junis Magic MCP server URL and your API key to your MCP client configuration. That's it.
***
## Why Junis Magic?
Let AI assistants build your agent teams. Describe what you need in natural language, and your assistant creates, configures, and wires everything together.
A comprehensive toolset across 16 categories — from creating agents to uploading documents, managing schedules, and configuring OAuth integrations.
Manage multiple organizations from a single MCP connection. Switch between teams effortlessly.
No API calls to write. No dashboard to navigate. Just tell your AI what you want.
***
## Getting Started
### Step 1: Get Your API Key
1. Log in to [junis.ai](https://junis.ai)
2. Go to **Settings > API Keys**
3. Create a **User API Key** (starts with `jns_user_`)
**User API Keys** are recommended — they work across all your organizations with a single key. You can create up to 5 user API keys.
### Step 2: Configure Your MCP Client
Add to your Claude Desktop configuration file:
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
```json theme={null}
{
"mcpServers": {
"junis-magic": {
"url": "https://mcp.junis.ai/mcp",
"transport": "streamable-http",
"headers": {
"Authorization": "Bearer jns_user_YOUR_API_KEY"
}
}
}
}
```
Add to `.cursor/mcp.json` in your project:
```json theme={null}
{
"mcpServers": {
"junis-magic": {
"url": "https://mcp.junis.ai/mcp",
"transport": "streamable-http",
"headers": {
"Authorization": "Bearer jns_user_YOUR_API_KEY"
}
}
}
}
```
Use these connection details for any MCP-compatible client:
| Setting | Value |
| - | - |
| **URL** | `https://mcp.junis.ai/mcp` |
| **Transport** | Streamable HTTP |
| **Auth Header** | `Authorization: Bearer jns_user_YOUR_API_KEY` |
### Step 3: Start Building
Once connected, ask your AI assistant to manage your Junis platform:
```
"Switch to my organization and show me all my agents."
"Create a customer support agent with web search enabled."
"Build a research team with a web scraper and a report writer working in sequence."
```
**Build-first**: For clear build requests, your assistant builds immediately with smart defaults and reports afterward — it only asks for confirmation on large builds (10+ agents), destructive changes, or high-cost operations.
***
## Available Tool Categories
Junis Magic provides a comprehensive toolset organized in 16 categories:
| Category | Key Capabilities |
| - | - |
| **Organization Management** | List, create, and switch between organizations |
| **Agent Management** | Create, update, clone agents (LLM, Sequential, Parallel, Loop) |
| **Sub-Agent Relationships** | Wire parent-child agent hierarchies, reorder execution |
| **MCP Platform Management** | Connect and configure MCP integrations for agents |
| **RAG Knowledge Base** | Create datastores, upload documents, track indexing |
| **Sessions** | List conversations, view message history, manage sessions |
| **Chat** | Send messages to agents, OpenAI-compatible completions |
| **Schedules** | Create and manage automated task schedules |
| **Skills** | Import prompt-template skills from GitHub or npm |
| **Composio OAuth** | Connect 200+ OAuth integrations (Gmail, Slack, GitHub, etc.) |
| **Team Builder** | Batch operations for creating entire agent teams at once |
| **Content Management** | Track AI-generated images, videos, and audio |
| **Soul & Identity** | Define your organization's AI personality and communication style |
| **Memory Management** | View and update user memory profiles |
| **RAG Upload** | Upload and manage knowledge base documents |
| **Device Management** | Connect and manage local devices for desktop agent control |
***
## Example Workflows
### Build a Complete Agent Team
```
You: "Create a content team with these agents:
1. A research agent that can search the web
2. A writer agent that creates articles
3. A reviewer agent that checks quality
Set them up as a sequential pipeline."
AI Assistant:
1. Creates 3 LLM agents with appropriate instructions
2. Creates a Sequential parent agent
3. Wires sub-agent relationships in order
4. Links web search capability to the research agent
Done! Team ready to use.
```
### Set Up a Knowledge Base
```
You: "Create a knowledge base called 'Product Docs' and upload the product manual."
AI Assistant:
1. Creates a new RAG DataStore
2. Uploads the document
3. Monitors indexing progress
4. Reports when the knowledge base is ready for search
```
### Configure OAuth Integrations
```
You: "Connect Gmail to my support agent so it can read and send emails."
AI Assistant:
1. Lists available Composio integrations
2. Links Gmail integration to the agent
3. Provides the OAuth URL for you to authorize
4. Confirms connection status
```
### Manage Soul & Identity
```
You: "Set our AI personality to be professional but approachable,
with a focus on data-driven recommendations."
AI Assistant:
1. Reads current soul and identity configuration
2. Updates soul.md with reasoning principles
3. Updates identity.md with communication style
4. Changes take effect on next conversation
```
***
## Team Builder: Batch Operations
For complex setups, the **Team Builder** tool lets you create entire agent architectures in a single call:
```
You: "Build me an e-commerce support team with:
- Product FAQ agent (with RAG knowledge base)
- Order tracking agent (with Composio for CRM)
- Escalation agent (routes complex issues)
All orchestrated through a main support agent."
AI Assistant uses apply_team_changes() to:
1. Create all 4 agents simultaneously
2. Configure each with appropriate models and instructions
3. Wire the orchestration hierarchy
4. Link MCP platforms and integrations
```
**Pro Tip**: The Team Builder is the fastest way to set up complex agent architectures. It handles all the wiring automatically.
***
## Supported Models
Junis Magic lets you configure agents with various AI models:
| Model | Best For |
| - | - |
| **Claude Sonnet 5** | Default — general purpose, strong tool calling, balanced cost/performance |
| **Claude Sonnet 4.6** | Stable Claude alternative |
| **Claude Opus 4.8** | Flagship — complex reasoning, advanced analysis |
| **Gemini 3.5 Flash / 3.1 Flash Lite** | Cost-effective, fast; Gemini is **required** for RAG agents |
| **GPT-5.x** | Alternative general purpose (e.g. GPT-5.5, GPT-5.4) |
**RAG Agents**: Must use Gemini models (Vertex AI Search requirement). Other models cannot use RAG tools.
Models are validated against the platform's live model registry — if you request an unavailable model, your assistant will suggest the closest supported one. Ask *"what models can I use?"* for the current list.
***
## Best Practices
**Recommended**:
* Always call `switch_organization` first when starting a session
* Use `list_agents` and `list_tools` to understand your current setup before making changes
* Use the Team Builder for creating multiple agents at once
* Use User API Keys for cross-organization management
* Test agents with `send_message` after creation
**Important Notes**:
* Agent names should use lowercase with hyphens (e.g., `my-agent`)
* RAG agents must use Gemini models
* Changes take effect on the next conversation with the agent
***
## Related Guides
Learn about agent types and orchestration
Configure MCP platforms
Connect OAuth-based services
Programmatic access via REST API
***
**Need Help?** Contact us at [contact@junis.ai](mailto:contact@junis.ai) for assistance with Junis Magic setup and configuration.
# Knowledge Graph Widget
Source: https://docs.junis.ai/guides/knowledge-graph-embed
Embed the interactive knowledge graph from your Knowledge base on any website — read-only, domain-restricted, optionally locked with a signed token
The knowledge graph widget puts the **ontology graph control tower** from **Team → Knowledge** on any website with one iframe. Visitors explore the entities and relations your organization has published — search, filter by type, and inspect a node's neighbours — without logging in.
**Prerequisites**:
* **OWNER** or **ADMIN** role in your Junis organization
* At least one **ready** source in **Team → Knowledge** (files, URLs, a JSON API, or a database)
* A website where you can paste an `
> Paste one `