Account & Platform
Platform-level endpoints. These sit above both agent and project scope: they let you manage your own Juglans account, link external identities, register OAuth2 clients (so third parties can SSO with Juglans as the IdP), connect GitHub, and administer the agents you own.
Most endpoints below require a JWT bearer — the access token returned from POST /api/auth/login/google. JWT auth uses the same Authorization: Bearer ... header as API keys; the server distinguishes by token shape. The public discovery / market-data endpoints in the next section take no auth at all.
Base URLs
- Production:
https://api.juglans.ai - Local dev:
http://localhost:3002
Public endpoints (no auth)
These respond unauthenticated — useful for discovery, OIDC handshakes, and read-only market data.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/prices |
List spot prices for all tracked symbols. |
| GET | /api/prices/{symbol} |
Spot price for one symbol (e.g. BTC, ETH). |
| GET | /api/markets/perps |
List Hyperliquid perp markets. |
| GET | /api/markets/perps/{symbol} |
One perp market by symbol. |
| GET | /api/markets/predictions |
List Polymarket prediction markets. |
| GET | /api/markets/predictions/{id} |
One prediction market by id. |
| GET | /.well-known/openid-configuration |
OIDC discovery document. |
| GET | /.well-known/jwks.json |
Public signing keys for verifying Juglans-issued JWTs. |
| GET | /oauth2/authorize |
OAuth2 / OIDC authorization endpoint (browser entry point). |
| POST | /oauth2/authorize |
Submit consent (browser-side form post). |
| POST | /oauth2/token |
Exchange authorization code or refresh token for tokens. |
| POST | /oauth2/revoke |
Revoke an issued access or refresh token. |
| GET | /oauth2/userinfo |
OIDC userinfo — returns the subject's profile claims. |
/oauth2/userinfo requires a Bearer access token issued by /oauth2/token, not a Juglans JWT — this is the standard OIDC contract for downstream relying parties.
Authentication
POST /api/auth/login/google
Exchange a Google ID token for a Juglans JWT. Creates the account on first call. Optional invite_code redeems an invite atomically (verified Google email must match the invite's bound email).
curl https://api.juglans.ai/api/auth/login/google \
-H "Content-Type: application/json" \
-d '{
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"invite_code": "abc-def-ghi"
}'
Request:
{
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"invite_code": "abc-def-ghi"
}
Response:
{
"access_token": "eyJhbGciOi...",
"refresh_token": "rt_...",
"token_type": "Bearer",
"expires_in": 3600,
"account": {
"id": "a1b2c3d4-1111-2222-3333-444455556666",
"account_type": "human",
"display_name": "Ada Lovelace",
"email": "ada@example.com",
"email_verified": true,
"avatar_url": "https://lh3.googleusercontent.com/...",
"status": "active",
"metadata": {},
"created_at": "2026-04-29T10:00:00Z",
"updated_at": "2026-04-29T10:00:00Z"
}
}
POST /api/auth/refresh
Trade a refresh token for a fresh access token.
Request:
{ "refresh_token": "rt_..." }
Response:
{
"access_token": "eyJhbGciOi...",
"refresh_token": "rt_...",
"token_type": "Bearer",
"expires_in": 3600
}
POST /api/auth/logout
Revoke the session backing the current access token.
curl -X POST https://api.juglans.ai/api/auth/logout \
-H "Authorization: Bearer eyJhbGciOi..."
Response: { "ok": true }
Account profile
GET /api/account
Fetch the authenticated account.
curl https://api.juglans.ai/api/account \
-H "Authorization: Bearer eyJhbGciOi..."
Response:
{
"account": {
"id": "a1b2c3d4-1111-2222-3333-444455556666",
"account_type": "human",
"display_name": "Ada Lovelace",
"email": "ada@example.com",
"email_verified": true,
"avatar_url": "https://...",
"status": "active",
"metadata": {},
"created_at": "2026-04-29T10:00:00Z",
"updated_at": "2026-04-29T10:00:00Z"
}
}
PATCH /api/account
Update the authenticated account's profile fields. All fields optional.
curl -X PATCH https://api.juglans.ai/api/account \
-H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" \
-d '{"display_name": "Ada L.", "avatar_url": "https://example.com/me.png"}'
Request:
{
"display_name": "Ada L.",
"avatar_url": "https://example.com/me.png",
"status": "active"
}
Response: same shape as GET /api/account.
Linked identities
External login providers attached to this account (Google, GitHub).
GET /api/account/identities
List identity links.
Response:
{
"identities": [
{
"id": "11111111-2222-3333-4444-555566667777",
"account_id": "a1b2c3d4-1111-2222-3333-444455556666",
"provider": "google",
"provider_sub": "104629876543210987654",
"email": "ada@example.com",
"metadata": { "picture": "https://..." },
"created_at": "2026-04-29T10:00:00Z"
}
]
}
POST /api/account/identities
Attach a new identity link manually. The GitHub OAuth flow (below) calls this internally.
Request:
{
"provider": "github",
"provider_sub": "9281467",
"email": "ada@example.com"
}
Response:
{
"identity": {
"id": "11111111-2222-3333-4444-555566667777",
"account_id": "a1b2c3d4-1111-2222-3333-444455556666",
"provider": "github",
"provider_sub": "9281467",
"email": "ada@example.com",
"metadata": {},
"created_at": "2026-04-29T10:00:00Z"
}
}
DELETE /api/account/identities/{id}
Unlink an identity. The id is the identity-link UUID, not the provider sub.
curl -X DELETE https://api.juglans.ai/api/account/identities/11111111-2222-3333-4444-555566667777 \
-H "Authorization: Bearer eyJhbGciOi..."
Response: { "ok": true }
Wards & guardians
Juglans models each agent as its own first-class account. A guardianship is the row that says "human account A controls agent account B." Wards are the agents I guard; guardians are the humans guarding me.
GET /api/account/wards
List agent accounts I guard.
Response:
{
"wards": [
{
"guardianship": {
"id": "gg11-...",
"guardian_account_id": "a1b2c3d4-...",
"ward_account_id": "ww22-...",
"role": "owner",
"created_at": "2026-04-29T10:00:00Z",
"updated_at": "2026-04-29T10:00:00Z"
},
"account": {
"id": "ww22-...",
"account_type": "agent",
"display_name": "Nora",
"email": null,
"email_verified": false,
"avatar_url": null,
"status": "active",
"metadata": {},
"created_at": "2026-04-29T10:00:00Z",
"updated_at": "2026-04-29T10:00:00Z"
}
}
]
}
POST /api/account/wards
Create a new agent account and establish guardianship over it.
Request:
{ "name": "Nora" }
name is optional — omit it and the server defaults to "New Agent".
Response:
{
"account": { "id": "ww22-...", "account_type": "agent", "display_name": "Nora", "...": "..." },
"guardianship": { "id": "gg11-...", "role": "owner", "...": "..." }
}
GET /api/account/guardians
List guardians of the authenticated account (i.e. who guards me — useful from an agent account's session).
Response:
{
"guardians": [
{
"guardianship_id": "gg11-...",
"role": "owner",
"guardian": {
"id": "a1b2c3d4-...",
"display_name": "Ada Lovelace",
"account_type": "human"
}
}
]
}
Sessions
GET /api/auth/sessions
List active sessions on this account (each POST /api/auth/login/google creates one).
Response:
{
"sessions": [
{
"id": "ss11-2222-3333-4444-555566667777",
"ip_address": "203.0.113.42",
"user_agent": "Mozilla/5.0 ...",
"created_at": "2026-04-29T10:00:00Z",
"expires_at": "2026-05-29T10:00:00Z"
}
]
}
DELETE /api/auth/sessions/{id}
Revoke a session by id. You can only revoke your own.
Response: { "ok": true }
OAuth2 client registration
Register an external app to use Juglans as an OIDC IdP. After registration, the client uses the standard OAuth2 endpoints (/oauth2/authorize, /oauth2/token, /oauth2/userinfo, plus /.well-known/openid-configuration for discovery) — not the /api/oauth2/clients ones, which are for managing clients.
POST /api/oauth2/clients
Register a new client. Returns the client_secret exactly once — store it.
Request:
{
"client_name": "Acme Dashboard",
"redirect_uris": ["https://acme.example.com/auth/callback"],
"client_uri": "https://acme.example.com",
"logo_uri": "https://acme.example.com/logo.png",
"grant_types": ["authorization_code"],
"scopes_allowed": ["openid", "profile", "email"],
"is_confidential": true
}
Response:
{
"client": {
"id": "c1c2c3c4-1111-2222-3333-444455556666",
"client_id": "jcl_a1b2c3d4e5f6...",
"client_name": "Acme Dashboard",
"redirect_uris": ["https://acme.example.com/auth/callback"],
"grant_types": ["authorization_code"],
"scopes_allowed": ["openid", "profile", "email"],
"is_confidential": true,
"created_at": "2026-04-29T10:00:00Z"
},
"client_secret": "jcs_e1f2a3b4..."
}
GET /api/oauth2/clients
List clients owned by the authenticated account.
Response:
{
"clients": [
{
"id": "c1c2c3c4-...",
"client_id": "jcl_a1b2c3d4...",
"client_name": "Acme Dashboard",
"redirect_uris": ["https://acme.example.com/auth/callback"],
"status": "active",
"created_at": "2026-04-29T10:00:00Z"
}
]
}
GET /api/oauth2/clients/{id}
Fetch a single client (full row). 403 if you're not the owner.
Response: { "client": { ... } } — see the handler at crates/jg-server/src/handlers/oauth2.rs::get_client for the full shape.
PATCH /api/oauth2/clients/{id}
Update mutable fields. All fields optional.
Request:
{
"client_name": "Acme Dashboard v2",
"redirect_uris": ["https://acme.example.com/auth/callback", "https://acme.example.com/auth/callback2"],
"client_uri": "https://acme.example.com",
"logo_uri": "https://acme.example.com/logo.png",
"status": "active"
}
Response: { "client": { ... } } — same shape as GET /api/oauth2/clients/{id}.
DELETE /api/oauth2/clients/{id}
Delete the client. Outstanding tokens issued to it remain valid until they expire.
Response: { "ok": true }
POST /api/oauth2/clients/{id}/rotate-secret
Generate a new client_secret and invalidate the old one. Returned exactly once.
Response:
{ "client_secret": "jcs_a1b2c3d4..." }
GitHub integration
Two flows live under /api/github/*: the App install flow (so Juglans can write commits to a user's repo on their behalf) and the OAuth user-authorization flow (so we learn the user's GitHub login + numeric id and store it as an identity link).
GET /api/github/install-url
Return the URL the SPA should open in a new tab to install the Juglans GitHub App. The redirect target is configured in GitHub App settings.
Response:
{ "url": "https://github.com/apps/juglans/installations/new" }
GET /api/github/callback?installation_id=...&setup_action=install
Called by the SPA after the user completes the GitHub-side install. Resolves the installation and persists a github_installations row on the calling account.
Response:
{
"installation": {
"id": 12345678,
"account_login": "ada-lovelace",
"target_type": "User"
}
}
GET /api/github/oauth/url
Return the GitHub user-authorization URL. Distinct from the install URL — this learns the user's GitHub identity (login + numeric id) so we can auto-fill collaborator invites.
Response:
{ "url": "https://github.com/login/oauth/authorize?client_id=Iv1.abc123..." }
POST /api/github/oauth/exchange
Exchange the OAuth code from the user-authorization redirect for a GitHub access token, fetch the GitHub profile, and persist as an account_identity_links row with provider=github.
Request:
{ "code": "abcdef0123456789" }
Response:
{
"identity_link": {
"id": "11111111-2222-3333-4444-555566667777",
"provider": "github",
"login": "ada-lovelace",
"email": "ada@example.com"
}
}
GET /api/github/installations
List GitHub App installations linked to the authenticated account. Pass ?with_repos=true to hydrate each item with the repos it grants — note this is n+1 calls to GitHub.
Response (no with_repos):
{
"items": [
{
"installation_id": 12345678,
"account_login": "ada-lovelace",
"target_type": "User"
}
]
}
Response (with_repos=true): same shape, plus a repos field on each item. See the handler at crates/jg-server/src/handlers/github.rs::list_installations for the full per-repo shape.
POST /api/github/installations/claim
Manual recovery for failed callbacks. The user looks up the installation id at https://github.com/settings/installations and pastes it here.
Request:
{ "installation_id": 12345678 }
Response: same as GET /api/github/callback.
DELETE /api/github/installations/{installation_id}
Unbind a previously-linked installation from this Juglans account. The GitHub-side install is untouched — to fully revoke the App, the user goes to GitHub's settings page.
Response: 204 No Content
Agent administration
Owner-side endpoints for the agents you guard. Every path validates that the caller's account is in account_guardianships for the target agent — a 404 (not 403) is returned for foreign agents to avoid leaking existence.
POST /api/agents
Create a new agent under the authenticated account. Returns the freshly-minted jg_a_* API key once — store it server-side immediately. The handler also seeds the agent's filesystem dir with default templates and (best-effort) creates a backing GitHub repo.
curl -X POST https://api.juglans.ai/api/agents \
-H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" \
-d '{"name": "Nora", "avatar_url": "https://example.com/nora.png"}'
Request:
{
"name": "Nora",
"avatar_url": "https://example.com/nora.png"
}
Response:
{
"agent": {
"id": "ag11-2222-3333-4444-555566667777",
"owner_account_id": "a1b2c3d4-1111-2222-3333-444455556666",
"name": "Nora",
"status": "active",
"test_mode": false,
"wallet_config": {},
"avatar_url": "https://example.com/nora.png",
"git_remote_url": "git@github.com:juglans-ai/agent-ag11-2222-3333-4444-555566667777.git",
"git_remote_mode": "juglans",
"created_at": "2026-04-29T10:00:00Z",
"updated_at": "2026-04-29T10:00:00Z"
},
"api_key": "jg_a_a1b2c3d4e5f6...",
"api_key_id": "kk11-2222-3333-4444-555566667777"
}
GET /api/agents
List agents owned by the authenticated account. Optional ?mode=live|test filters by test_mode.
Response:
{
"agents": [
{
"id": "ag11-2222-3333-4444-555566667777",
"name": "Nora",
"status": "active",
"test_mode": false,
"avatar_url": "https://example.com/nora.png",
"created_at": "2026-04-29T10:00:00Z",
"updated_at": "2026-04-29T10:00:00Z"
}
]
}
GET /api/agents/{id}
Fetch one agent with its wallets and connected exchanges.
Response:
{
"agent": { "id": "ag11-...", "name": "Nora", "status": "active", "...": "..." },
"wallets": [
{ "id": "iw_clx...", "chain": "ethereum", "address": "0xAb12...CdEf" }
],
"exchanges": [
{ "exchange": "hyperliquid", "permissions": ["read","trade"], "connected_at": "2026-04-29T10:00:00Z" }
]
}
PATCH /api/agents/{id}
Update mutable agent fields. All optional. Pass status: "frozen" to disable trading without deleting the agent.
Request:
{
"name": "Nora v2",
"status": "active",
"avatar_url": "https://example.com/nora2.png",
"wallet_config": { "default_chain": "ethereum" }
}
Response: { "agent": { ... } } — same shape as GET /api/agents/{id}'s agent field.
POST /api/agents/{id}/keys
Mint a new jg_a_* API key for the agent. The plaintext key is returned exactly once; only the hash is persisted. Always issued with the full scope set: read, trade, transfer, admin.
curl -X POST https://api.juglans.ai/api/agents/ag11-2222-3333-4444-555566667777/keys \
-H "Authorization: Bearer eyJhbGciOi..."
Response:
{
"api_key": "jg_a_b2c3d4e5f6a7...",
"api_key_id": "kk22-3333-4444-5555-666677778888",
"prefix": "jg_a_b2c3d4e"
}
POST /api/agents/{id}/avatar
Upload an avatar via multipart/form-data with a single file field. Cap: 3 MiB request body, 2 MiB image. Allowed content types: image/png, image/jpeg, image/webp, image/gif. The server stores under /uploads/agents/{id}.{ext} and updates avatar_url with a cache-busted query string.
curl -X POST https://api.juglans.ai/api/agents/ag11-2222-3333-4444-555566667777/avatar \
-H "Authorization: Bearer eyJhbGciOi..." \
-F "file=@./nora.png;type=image/png"
Response: { "agent": { ... } } with the updated avatar_url.
POST /api/agents/{id}/wallets
Provision a new server-custodied wallet via Privy and bind it to the agent.
Request:
{ "chain_type": "ethereum" }
Response:
{
"id": "ww11-2222-3333-4444-555566667777",
"chain": "ethereum",
"address": "0xAb12...CdEf",
"privy_wallet_id": "iw_clxabc123..."
}
POST /api/agents/{id}/card
Issue a virtual card for the agent (Lithic-backed). Card data including PAN/CVV is returned on the create response — capture it now.
Response:
{
"card": {
"token": "card_01H8X7...",
"last_four": "4242",
"type": "VIRTUAL",
"exp_month": "04",
"exp_year": "2030",
"spend_limit": 10000,
"state": "OPEN",
"pan": "4111111111114242",
"cvv": "123"
}
}
GET /api/agents/{id}/card
List cards issued to this agent. PAN/CVV are not returned here — only the safe metadata.
Response:
{
"cards": [
{
"token": "card_01H8X7...",
"last_four": "4242",
"type": "VIRTUAL",
"exp_month": "04",
"exp_year": "2030",
"spend_limit": 10000,
"state": "OPEN"
}
]
}
PUT /api/agents/{id}/limits
Set or update a spending limit. Periods are minute, hour, or day. asset defaults to USDC.
Request:
{ "period": "day", "max_amount": "500.00", "asset": "USDC" }
Response:
{
"limits": [
{ "period": "day", "max_amount": "500.00", "asset": "USDC" }
]
}
GET /api/agents/{id}/limits
List configured limits for the agent.
Response: same shape as PUT /api/agents/{id}/limits.
GET /api/agents/{id}/audit
Recent audit-log entries (every API-key request that hit the agent). Optional ?limit=N (default 50, max 200).
Response:
{
"audit_log": [
{
"id": "ll11-2222-3333-4444-555566667777",
"action": "place_order",
"details": { "symbol": "BTC", "side": "buy", "size": "0.1" },
"spending_limit_remaining": "499.40",
"created_at": "2026-04-29T10:05:12Z"
}
]
}
POST /api/agents/{id}/exchanges
Connect the agent to a centralized exchange. Sends on-chain approvals via Privy (USDC for polymarket on Polygon, USDC for hyperliquid on Arbitrum) when the agent is in live mode.
Request:
{ "exchange": "hyperliquid" }
Response:
{
"exchange": "hyperliquid",
"status": "active",
"wallet": "0xAb12...CdEf",
"tx_hashes": ["0x9f8e7d..."]
}
DELETE /api/agents/{id}/exchanges/{ex_id}
Disconnect the named exchange. {ex_id} is the exchange short name (e.g. hyperliquid), not a UUID.
Response: { "status": "disconnected", "exchange": "hyperliquid" }
GET /api/agents/{id}/memories
List the agent's filesystem-backed memory entries (owner-side view).
Response:
{
"memories": [
{
"key": "trading_style",
"content": "Conservative — never more than 5% of NAV in a single position.",
"metadata": {},
"updated_at": "2026-04-29T10:00:00Z",
"updated_by": "user"
}
]
}
GET /api/agents/{id}/memories/{key}
Fetch one memory entry by key.
Response: a single entry object with the same fields as items in memories[] above.
PUT /api/agents/{id}/memories/{key}
Owner-side write. Owner writes always set updated_by = "user", which the agent-side write path respects (the agent must pass force=true to overwrite a user-edited memory).
Request:
{
"content": "Conservative — never more than 5% of NAV in a single position.",
"metadata": { "source": "owner" }
}
Response: the written entry.
DELETE /api/agents/{id}/memories/{key}
Delete a memory entry. Response: { "deleted": true }.
GET /api/agents/{id}/skill.md
Download a personalized Claude Code skill bundle for this agent. Response is text/markdown; charset=utf-8 with Content-Disposition: attachment; filename="juglans-{slug}.skill.md".
GET /api/agents/{id}/openapi.yaml
Personalized OpenAPI 3 spec for ChatGPT Actions / Manus / any OpenAPI-aware host. Response is application/yaml.
GET /api/agents/{id}/gpt-instructions.txt
Custom GPT Instructions text. Response is text/plain.
GET /api/agents/{id}/cursorrules
.cursorrules text — drop into a repo root for Cursor. Response is text/plain.
GET /api/agents/{id}/chat-workflow
Read the agent's chat workflow files: chat.jg, juglans.toml, and prompts/system_prompt.jgx. Each field is paired with an is_default_* boolean indicating whether the on-disk content matches the bundled default.
Response:
{
"chat_jg": "flow chat { ... }",
"is_default_chat_jg": true,
"juglans_toml": "[ai.providers.juglans]\nbase_url = \"https://api.juglans.ai/api/llm\"\n...",
"is_default_juglans_toml": true,
"system_prompt_jgx": "You are Nora, an autonomous trading agent...",
"is_default_system_prompt_jgx": false
}
PUT /api/agents/{id}/chat-workflow
Update any subset of the three workflow files. Writes commit + push to the agent's git remote when bound.
Request:
{
"chat_jg": "flow chat { ... }",
"juglans_toml": "[ai.providers.juglans]\n...",
"system_prompt_jgx": "You are Nora..."
}
Response: { "updated": true }
DELETE /api/agents/{id}/chat-workflow?field=…
Reset one or all workflow files to their bundled defaults. field must be chat_jg, juglans_toml, system_prompt_jgx, or all.
Response: { "reset": true }
PATCH /api/agents/{id}/git
Rebind the agent's repository to a user-owned GitHub repo reached through a previously-installed App installation. Force-pushes the agent's working copy into the new repo and installs a webhook on it.
Request:
{
"installation_id": 12345678,
"repo": "ada-lovelace/nora-agent"
}
The body also accepts split fields (owner + name) in lieu of repo. Response:
{
"agent": { "id": "ag11-...", "git_remote_url": "git@github.com:ada-lovelace/nora-agent.git", "git_remote_mode": "byo", "git_installation_id": 12345678, "...": "..." }
}
POST /api/agents/{id}/repo/grant-access
Invite a GitHub user to the agent's hosted repo as a collaborator. Solves the visibility problem in Juglans-hosted mode where the repo is private under the platform org.
Request:
{ "github_username": "ada-lovelace", "permission": "push" }
permission defaults to push; valid values follow GitHub's collaborator permission model (pull, triage, push, maintain, admin). Response:
{
"ok": true,
"owner": "juglans-ai",
"repo": "agent-ag11-2222-3333-4444-555566667777",
"github_username": "ada-lovelace",
"permission": "push"
}
GET /api/skills
List the server's bundled starter-skill catalog. Identical for every authenticated user; returns the parsed YAML frontmatter (slug, description) and the full .jgx body so the frontend editor can render them as read-only tabs.
Response:
{
"skills": [
{
"filename": "place-order.jgx",
"slug": "place-order",
"description": "Place a perp order on Hyperliquid.",
"content": "---\nslug: place-order\ndescription: ...\n---\n...",
"is_bundled": true
}
]
}
Owner-side conversations
The web UI stores chat history server-side so threads persist across reloads and can be enumerated in a sidebar. These endpoints are JWT-only — they're the owner's conversations, distinct from the per-project conversations the company exposes via jg_p_* keys (see the projects doc).
POST /api/chat
Server-Sent-Events streaming chat with one of the caller's agents. Auto-creates a conversation on first call (returned in the first SSE event as {type: "meta", conversation_id: "..."}); pass conversation_id back on subsequent calls to keep the same thread.
curl -N https://api.juglans.ai/api/chat \
-H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" \
-d '{
"agent_id": "ag11-2222-3333-4444-555566667777",
"message": "What positions do we hold on Hyperliquid right now?",
"conversation_id": "cv11-2222-3333-4444-555566667777"
}'
Request:
{
"agent_id": "ag11-2222-3333-4444-555566667777",
"message": "What positions do we hold on Hyperliquid right now?",
"conversation_id": "cv11-2222-3333-4444-555566667777"
}
conversation_id is optional. Response is text/event-stream with these event payload shapes:
data: {"type":"meta","conversation_id":"cv11-2222-3333-4444-555566667777"}
data: {"type":"content","text":"You currently hold..."}
data: {"type":"content","text":" 0.5 BTC perp at $..."}
data: {"type":"done"}
Errors arrive as {type: "error", message: "..."}. The server persists the user message before invoking juglans and the assistant reply once the stream completes — a client disconnect mid-stream does not lose either.
GET /api/conversations
List the caller's conversations with derived participant lists.
Response:
{
"conversations": [
{
"id": "cv11-2222-3333-4444-555566667777",
"account_id": "a1b2c3d4-1111-2222-3333-444455556666",
"project_id": null,
"external_user_id": null,
"title": "Daily perp review",
"created_at": "2026-04-29T09:00:00Z",
"last_message_at": "2026-04-29T10:05:12Z",
"archived_at": null,
"agent_ids": ["ag11-2222-3333-4444-555566667777"]
}
]
}
POST /api/conversations
Create an empty conversation. Useful when you want to set the title before the first message.
Request:
{ "title": "Daily perp review" }
Response: { "conversation": { ... } } — the freshly-created row with agent_ids: [].
GET /api/conversations/{id}
Fetch one conversation along with its full message log. 404 if the row's account_id doesn't match the caller (existence is hidden across owners).
Response:
{
"conversation": { "id": "cv11-...", "title": "Daily perp review", "...": "..." },
"messages": [
{
"id": "mg11-2222-3333-4444-555566667777",
"role": "user",
"agent_id": null,
"content": "What positions do we hold on Hyperliquid right now?",
"created_at": "2026-04-29T10:05:00Z"
},
{
"id": "mg22-3333-4444-5555-666677778888",
"role": "assistant",
"agent_id": "ag11-2222-3333-4444-555566667777",
"content": "You currently hold 0.5 BTC perp...",
"created_at": "2026-04-29T10:05:12Z"
}
]
}
PATCH /api/conversations/{id}
Rename or (un)archive a conversation. Both fields optional; archived: true archives, false restores, null/omitted leaves alone.
Request:
{ "title": "Daily perp review (Q2)", "archived": false }
Response: { "conversation": { ... } } with the updated metadata.
DELETE /api/conversations/{id}
Permanently delete the conversation and its messages.
Response: 204 No Content
Wallet inspection
Read-only proxies to Privy for the wallets attached to your agents. Wallet creation lives under the agent-administration section above (POST /api/agents/{id}/wallets); these routes only inspect.
GET /api/wallets/{wallet_id}
Fetch wallet details from Privy. {wallet_id} is the Privy id (e.g. iw_clxabc123...), not the database row id.
curl https://api.juglans.ai/api/wallets/iw_clxabc123... \
-H "Authorization: Bearer eyJhbGciOi..."
Response: the raw Privy wallet object — see the handler at crates/jg-server/src/handlers/wallet.rs::get_wallet for the full shape.
GET /api/wallets/{wallet_id}/balance
Fetch a balance. Optional query params: chain (default ethereum) and asset (default eth).
curl "https://api.juglans.ai/api/wallets/iw_clxabc123.../balance?chain=eip155:1&asset=native" \
-H "Authorization: Bearer eyJhbGciOi..."
Response:
{
"balances": [
{ "chain": "eip155:1", "asset": "native", "amount": "0.4210", "decimals": 18 }
]
}
Endpoint index
Full table of routes covered by this document. Auth column: None = no token, JWT = Juglans access token from /api/auth/login/google, OIDC = Bearer access token issued by /oauth2/token (relying-party use), HMAC = GitHub-signed webhook payload.
| Method | Path | Auth |
|---|---|---|
| GET | /api/prices |
None |
| GET | /api/prices/{symbol} |
None |
| GET | /api/markets/perps |
None |
| GET | /api/markets/perps/{symbol} |
None |
| GET | /api/markets/predictions |
None |
| GET | /api/markets/predictions/{id} |
None |
| GET | /.well-known/openid-configuration |
None |
| GET | /.well-known/jwks.json |
None |
| GET | /oauth2/authorize |
None |
| POST | /oauth2/authorize |
None |
| POST | /oauth2/token |
None |
| POST | /oauth2/revoke |
None |
| GET | /oauth2/userinfo |
OIDC |
| POST | /api/auth/login/google |
None |
| POST | /api/auth/refresh |
None |
| POST | /api/auth/logout |
JWT |
| GET | /api/auth/sessions |
JWT |
| DELETE | /api/auth/sessions/{id} |
JWT |
| GET | /api/account |
JWT |
| PATCH | /api/account |
JWT |
| GET | /api/account/identities |
JWT |
| POST | /api/account/identities |
JWT |
| DELETE | /api/account/identities/{id} |
JWT |
| GET | /api/account/wards |
JWT |
| POST | /api/account/wards |
JWT |
| GET | /api/account/guardians |
JWT |
| POST | /api/oauth2/clients |
JWT |
| GET | /api/oauth2/clients |
JWT |
| GET | /api/oauth2/clients/{id} |
JWT |
| PATCH | /api/oauth2/clients/{id} |
JWT |
| DELETE | /api/oauth2/clients/{id} |
JWT |
| POST | /api/oauth2/clients/{id}/rotate-secret |
JWT |
| GET | /api/github/install-url |
JWT |
| GET | /api/github/callback |
JWT |
| GET | /api/github/oauth/url |
JWT |
| POST | /api/github/oauth/exchange |
JWT |
| GET | /api/github/installations |
JWT |
| POST | /api/github/installations/claim |
JWT |
| DELETE | /api/github/installations/{installation_id} |
JWT |
| POST | /api/agents |
JWT |
| GET | /api/agents |
JWT |
| GET | /api/agents/{id} |
JWT |
| PATCH | /api/agents/{id} |
JWT |
| POST | /api/agents/{id}/keys |
JWT |
| POST | /api/agents/{id}/avatar |
JWT |
| POST | /api/agents/{id}/wallets |
JWT |
| POST | /api/agents/{id}/card |
JWT |
| GET | /api/agents/{id}/card |
JWT |
| PUT | /api/agents/{id}/limits |
JWT |
| GET | /api/agents/{id}/limits |
JWT |
| GET | /api/agents/{id}/audit |
JWT |
| POST | /api/agents/{id}/exchanges |
JWT |
| DELETE | /api/agents/{id}/exchanges/{ex_id} |
JWT |
| GET | /api/agents/{id}/memories |
JWT |
| GET | /api/agents/{id}/memories/{key} |
JWT |
| PUT | /api/agents/{id}/memories/{key} |
JWT |
| DELETE | /api/agents/{id}/memories/{key} |
JWT |
| GET | /api/agents/{id}/skill.md |
JWT |
| GET | /api/agents/{id}/openapi.yaml |
JWT |
| GET | /api/agents/{id}/gpt-instructions.txt |
JWT |
| GET | /api/agents/{id}/cursorrules |
JWT |
| GET | /api/agents/{id}/chat-workflow |
JWT |
| PUT | /api/agents/{id}/chat-workflow |
JWT |
| DELETE | /api/agents/{id}/chat-workflow |
JWT |
| PATCH | /api/agents/{id}/git |
JWT |
| POST | /api/agents/{id}/repo/grant-access |
JWT |
| GET | /api/skills |
JWT |
| POST | /api/chat |
JWT |
| GET | /api/conversations |
JWT |
| POST | /api/conversations |
JWT |
| GET | /api/conversations/{id} |
JWT |
| PATCH | /api/conversations/{id} |
JWT |
| DELETE | /api/conversations/{id} |
JWT |
| GET | /api/wallets/{wallet_id} |
JWT |
| GET | /api/wallets/{wallet_id}/balance |
JWT |