API reference
juras.io exposes a REST API built around sessions. POST /api/v1/sessions opens a session and ingests your files, GET /sessions/{id}/grep queries them, PUT /sessions/{id}/append adds more files, and DELETE /sessions/{id} tears it down. That's the whole surface.
●Base URL: https://api.juras.io
Content type: JSON for responses, multipart/form-data for file uploads.
Authentication
All requests require a bearer token in the Authorization header. Generate keys from your dashboard. Keys are scoped to a single organisation; rotate them at any time.
shell
Authorization: Bearer juras_live_8f3kQz...
Keys never appear in URLs or query strings. We hash them at rest and only display the full value once at creation.
Quickstart
Five endpoints, in order of a typical session lifecycle:
POST/api/v1/sessions— upload files, get a session ID
GET/api/v1/sessions/{id}/grep— query the session
PUT/api/v1/sessions/{id}/append— add more files later
DELETE/api/v1/sessions/{id}— stop (soft) or permanently delete
POST/api/v1/sessions/{id}/restart— re-hydrate an S3 session
Errors & rate limits
Errors return JSON: { "error": "human-readable message" }. Common status codes:
| Field | Type | Description |
|---|
| 400 | invalid_request | Malformed body or missing required fields. |
| 401 | missing_auth | Bearer token absent or unrecognised. |
| 403 | quota_exceeded | Plan limit hit. Top up or upgrade. |
| 404 | session_not_found | Session expired, permanently deleted, or ID typo. |
| 413 | payload_too_large | Upload exceeds plan ingest limit. |
| 429 | rate_limited | Too many requests per second; back off. |
| 5xx | internal | Our problem. Retry with exponential backoff. |
Rate limits scale with your plan. Free is 10 req/s, Pro 100 req/s, Scale 1,000 req/s, Custom negotiated.
POST /api/v1/sessions
Open a new session and ingest one or more text files. Returns a session id to use on subsequent calls.
Content-Type: multipart/form-data
| Field | Type | Description |
|---|
| filesrequired | file | One or more text files. Any text format; .zip archives are extracted and directory structure preserved. |
| storage | string | Storage backend. memory (default, TTL-scoped) or s3 (persists across TTL, restartable). |
| ttl_minutes | integer | Session lifetime in minutes. Default 10, applies to memory sessions only. |
response · 201 Created
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"storage": "memory",
"file_count": 2,
"expires_at": "2026-05-07T12:10:00Z"
}GET /api/v1/sessions/{id}/grep
Run a grep query against the active session. Returns file hits with line numbers and snippets. S3 sessions are automatically re-hydrated if not currently in memory.
Query parameters:
| Field | Type | Description |
|---|
| queryrequired | string | Pattern to search for. |
| mode | string | plain (default), regex, or fuzzy. |
| smart_case | boolean | Case-insensitive unless the query contains an uppercase letter. Default false. |
| before_context | integer | Lines of context before each match. Default 0. |
| after_context | integer | Lines of context after each match. Default 0. |
| page_limit | integer | Max files to return per page. Default 20. |
| file_offset | integer | File pagination offset. Default 0. |
| classify_definitions | boolean | Annotate matches that appear to be symbol definitions. Default false. |
response · 200 OK
{
"hits": [
{
"path": "src/loop.ts",
"matches": [
{ "line": 142, "snippet": "export async function runAgentLoop(..." }
]
}
],
"total": 1
}PUT /api/v1/sessions/{id}/append
Add files to an existing session without re-uploading the original payload. Useful when an agent receives new context mid-loop. Set the filename to the full relative path to preserve directory structure.
Content-Type: multipart/form-data
| Field | Type | Description |
|---|
| filesrequired | file | One or more text files to add. Zip archives are extracted. |
response · 200 OK
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"files_added": 3
}DELETE /api/v1/sessions/{id}
Stop a session. By default this is a soft stop: in-memory resources are freed but S3 files are kept, so the session can be restarted later. Pass ?permanently=true to also delete S3 storage.
Query parameters:
| Field | Type | Description |
|---|
| permanently | boolean | If true, deletes S3 files permanently. The database row is kept but the workspace cannot be restarted. Irreversible. Default false. |
response · 200 OK
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"deleted": true
}POST /api/v1/sessions/{id}/restart
Re-hydrate an S3-backed session from storage. Use this after a soft stop to bring the session back into memory without re-uploading files. Returns 400 for memory-only sessions.
response · 200 OK
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"storage": "s3",
"file_count": 2
}curl
Open a session with two files, grep, append a third, then clean up.
shell
# 1. Create session
SESSION=$(curl -s -X POST https://api.juras.io/api/v1/sessions \
-H "Authorization: Bearer $JURAS_KEY" \
-F "files=@./src/loop.ts;filename=src/loop.ts" \
-F "files=@./README.md;filename=README.md" \
| jq -r .id)
# 2. Grep
curl -sG "https://api.juras.io/api/v1/sessions/$SESSION/grep" \
-H "Authorization: Bearer $JURAS_KEY" \
--data-urlencode "query=runAgentLoop" \
| jq .
# 3. Append a file
curl -s -X PUT "https://api.juras.io/api/v1/sessions/$SESSION/append" \
-H "Authorization: Bearer $JURAS_KEY" \
-F "files=@./src/tools.ts;filename=src/tools.ts"
# 4. Stop
curl -s -X DELETE "https://api.juras.io/api/v1/sessions/$SESSION" \
-H "Authorization: Bearer $JURAS_KEY"
Node (http.Agent)
Reuse a single keep-alive socket across all calls. The http.Agent pattern avoids the TLS handshake overhead on every request — important when appending many small trace files.
session.mjs
import http from "node:http";
import fs from "node:fs";
// Reusable keep-alive agent
const agent = new http.Agent({ keepAlive: true, maxSockets: 8 });
const headers = { Authorization: `Bearer ${process.env.JURAS_KEY}` };
// 1. POST /api/v1/sessions
const form = new FormData();
form.set("files",
new Blob([fs.readFileSync("./src/loop.ts")], { type: "text/plain" }),
"src/loop.ts");
const { id } = await fetch("https://api.juras.io/api/v1/sessions", {
method: "POST", agent, headers, body: form,
}).then(r => r.json());
// 2. GET /api/v1/sessions/{id}/grep
const url = new URL(`https://api.juras.io/api/v1/sessions/${id}/grep`);
url.searchParams.set("query", "runAgentLoop");
const { hits } = await fetch(url, { agent, headers }).then(r => r.json());
console.log(hits);
// 3. PUT /api/v1/sessions/{id}/append
const appendForm = new FormData();
appendForm.set("files",
new Blob([fs.readFileSync("./src/tools.ts")], { type: "text/plain" }),
"src/tools.ts");
await fetch(`https://api.juras.io/api/v1/sessions/${id}/append`, {
method: "PUT", agent, headers, body: appendForm,
});
// 4. DELETE /api/v1/sessions/{id}
await fetch(`https://api.juras.io/api/v1/sessions/${id}`, {
method: "DELETE", agent, headers,
});Python (httpx)
session.py
import os, httpx
KEY = os.environ["JURAS_KEY"]
client = httpx.Client(
base_url="https://api.juras.io",
headers={"Authorization": f"Bearer {KEY}"},
timeout=30,
)
# 1. POST /api/v1/sessions — paths preserved via explicit tuple form
start = client.post("/api/v1/sessions", files=[
("files", ("src/loop.ts", open("./src/loop.ts", "rb"))),
("files", ("README.md", open("./README.md", "rb"))),
]).json()
session_id = start["id"]
# 2. GET /api/v1/sessions/{id}/grep
hits = client.get(f"/api/v1/sessions/{session_id}/grep", params={
"query": "runAgentLoop",
}).json()
print(hits)
# 3. PUT /api/v1/sessions/{id}/append
client.put(f"/api/v1/sessions/{session_id}/append", files=[
("files", ("src/tools.ts", open("./src/tools.ts", "rb"))),
])
# 4. DELETE /api/v1/sessions/{id}
client.delete(f"/api/v1/sessions/{session_id}")