juras.iobeta
Sign inStart free

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:

FieldTypeDescription
400invalid_requestMalformed body or missing required fields.
401missing_authBearer token absent or unrecognised.
403quota_exceededPlan limit hit. Top up or upgrade.
404session_not_foundSession expired, permanently deleted, or ID typo.
413payload_too_largeUpload exceeds plan ingest limit.
429rate_limitedToo many requests per second; back off.
5xxinternalOur 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

FieldTypeDescription
filesrequiredfileOne or more text files. Any text format; .zip archives are extracted and directory structure preserved.
storagestringStorage backend. memory (default, TTL-scoped) or s3 (persists across TTL, restartable).
ttl_minutesintegerSession 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:

FieldTypeDescription
queryrequiredstringPattern to search for.
modestringplain (default), regex, or fuzzy.
smart_casebooleanCase-insensitive unless the query contains an uppercase letter. Default false.
before_contextintegerLines of context before each match. Default 0.
after_contextintegerLines of context after each match. Default 0.
page_limitintegerMax files to return per page. Default 20.
file_offsetintegerFile pagination offset. Default 0.
classify_definitionsbooleanAnnotate 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

FieldTypeDescription
filesrequiredfileOne 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:

FieldTypeDescription
permanentlybooleanIf 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}")