# API Reference

All endpoints are relative to `https://jarvis.spacekeep.dev` and speak JSON.

## Conventions

- **Auth** is shown per endpoint: *None*, *Session*, or *Key* — *Key* also accepts a session.
- Length limits return `400` when exceeded.
- Requesting an identifier that belongs to another account returns `404`, not `403`, so ids cannot
  be probed.
- Timestamps are ISO 8601 UTC strings.

## Error shape

```json
{ "error": "Message content is required." }
```

| Status | Meaning |
| --- | --- |
| `400` | Invalid body, missing field, or a value over its limit |
| `401` | Missing, invalid, or revoked credential |
| `403` | The account is restricted — see [Global Enforcement](/jarvis/security/global-enforcement/) |
| `404` | No such resource for this account |
| `409` | Already exists |
| `429` | Rate limit reached |
| `500` | Server-side failure |
| `503` | Access could not be verified right now |
| `504` | The reply took too long |

---

## Chat {#chat}

### `POST /api/jarvis/chat` — Key or Session

Ask JARVIS a question. Returns a complete reply.

**Request**

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `message` | string | Yes | 1–8,000 characters |
| `conversationId` | string | No | Continue an existing conversation for context |
| `messages` | array | No | Prior turns, for stateless calls only |

**Response**

| Field | Type | Notes |
| --- | --- | --- |
| `reply` | string | JARVIS's answer, in Markdown |
| `conversationId` | string | The conversation the exchange belongs to |
| `sources` | array | Present only when a search ran — see below |
| `toolRun` | object | Present only when a capability actually ran |
| `policyDenied` | boolean | Present only when a policy denial replaced the reply |
| `nameSuggestion` | object | Present only when JARVIS detected a preferred-name statement |

A `source` object has `title`, `url`, `domain`, `snippet`, and `favicon`.

A `toolRun` object has `state` (`completed` or `error`), a short `caption`, and a `steps` array of
`{ label, state }`.

Responses also carry internal metadata fields that are **not** part of this public contract and may
change at any time. Depend only on the fields documented above.

```bash
curl -s https://jarvis.spacekeep.dev/api/jarvis/chat \
  -H "Authorization: Bearer $JARVIS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message":"What is the latest Node.js version? Search this on Google."}'
```

```json
{
  "reply": "Node.js 24 is the current release line…",
  "conversationId": "0f3a9c1e-…",
  "toolRun": {
    "state": "completed",
    "caption": "Search completed — 5 results.",
    "steps": [
      { "label": "Thinking", "state": "done" },
      { "label": "Searching the web", "state": "done" },
      { "label": "Reviewing results", "state": "done" },
      { "label": "Preparing response", "state": "done" }
    ]
  },
  "sources": [
    {
      "title": "Node.js — Official Release",
      "url": "https://nodejs.org/en/blog/release/v24.0.0",
      "domain": "nodejs.org",
      "snippet": "…",
      "favicon": "…"
    }
  ]
}
```

> [!TIP]
> Omit `conversationId` for a one-off call. Including `messages` gives the conversation context for
> that call without storing anything. For anything ongoing, create a conversation and pass its id
> so history is preserved.

**Errors**

| Status | Cause |
| --- | --- |
| `400` | Invalid JSON, empty `message`, or `message` over 8,000 characters |
| `401` | No credential, or `Invalid API key.` |
| `404` | `conversationId` does not exist for this account |
| `429` | Over 40 requests per minute |
| `503` | Access could not be verified |
| `504` | The reply took too long — retry with a shorter prompt |

---

## Conversations {#conversations}

A conversation object is `{ id, title, createdAt, updatedAt }`. A message object is
`{ id, role, content, policyDenied, createdAt }`, where `role` is `user` or `assistant`.

Only conversations that contain at least one message are listed.

### `GET /api/conversations` — Key or Session

Recent conversations, newest first. Up to 100 are returned.

| Query | Notes |
| --- | --- |
| `q` | Search titles and message content. Up to 80 characters |

```json
{ "conversations": [{ "id": "0f3a…", "title": "Node.js release notes", "createdAt": "…", "updatedAt": "…" }] }
```

`400` if `q` is longer than 80 characters.

### `POST /api/conversations` — Key or Session

Create a conversation.

| Body | Type | Notes |
| --- | --- | --- |
| `title` | string | Optional, up to 80 characters. Defaults to `New conversation` |

Returns `201` with `{ "conversation": { … } }`. `400` if `title` is over 80 characters.

### `GET /api/conversations/:id` — Key or Session

Returns `{ "conversation": { … }, "messages": [ … ] }`.

`404` if the conversation does not exist for this account.

### `PATCH /api/conversations/:id` — Key or Session

Rename a conversation.

| Body | Type | Required | Notes |
| --- | --- | --- | --- |
| `title` | string | Yes | 1–80 characters, single line |

Returns `{ "conversation": { … } }`.

`400` if `title` is missing, empty, over 80 characters, or contains control characters. `404` if the
conversation does not exist for this account.

### `DELETE /api/conversations/:id` — Key or Session

Deletes the conversation and its messages. Returns `{ "ok": true }`.

`404` if the conversation does not exist for this account.

### `POST /api/conversations/:id/messages` — Key or Session

Store a user-authored message in a conversation.

| Body | Type | Required | Notes |
| --- | --- | --- | --- |
| `content` | string | Yes | 1–8,000 characters |

Returns `201` with `{ "message": { … }, "conversation": { … } }`.

`400` if `content` is missing or over 8,000 characters. `404` if the conversation does not exist
for this account.

> [!NOTE]
> This endpoint records a message. It does not generate a reply — use
> [`POST /api/jarvis/chat`](#chat) for that.

---

## Tasks {#tasks}

A task object is
`{ id, title, status, createdBy, createdAt, updatedAt, completedAt }`, where `status` is `open` or
`done` and `createdBy` is `user` or `assistant`.

### `GET /api/tasks` — Key or Session

| Query | Notes |
| --- | --- |
| `status` | `open` or `done`. Omit for all |

```json
{ "tasks": [{ "id": "…", "title": "Renew the domain", "status": "open", "createdBy": "user", "createdAt": "…", "updatedAt": "…", "completedAt": null }] }
```

### `POST /api/tasks` — Key or Session

| Body | Type | Required | Notes |
| --- | --- | --- | --- |
| `title` | string | Yes | 1–200 characters |

Returns `201` with `{ "task": { … } }`. `400` if the title is missing or outside the range.

### `PATCH /api/tasks/:id` — Key or Session

Update a task. Send `title`, `status`, or both.

| Body | Type | Notes |
| --- | --- | --- |
| `title` | string | 1–200 characters |
| `status` | string | `open` or `done` |

Returns `{ "task": { … } }`. Setting `done` records `completedAt`; setting `open` clears it, so
completion is reversible.

`400` if nothing valid was sent or `status` is not `open` or `done`. `404` if the task does not
exist for this account.

### `DELETE /api/tasks/:id` — Key or Session

Returns `{ "ok": true }`. `404` if the task does not exist for this account.

---

## Memories {#memories}

Session-only. A memory object is
`{ id, type, content, fromConversation, createdAt, updatedAt }`, where `type` is `user`,
`preference`, or `project`.

### `GET /api/memories` — Session

| Query | Notes |
| --- | --- |
| `type` | `user`, `preference`, or `project`. Omit for all |

```json
{ "memories": [{ "id": "…", "type": "preference", "content": "Prefers TypeScript over JavaScript", "fromConversation": true, "createdAt": "…", "updatedAt": "…" }] }
```

`fromConversation` is a hint that the memory was saved from a conversation.

### `PATCH /api/memories/:id` — Session

Edit a memory in place.

| Body | Type | Required | Notes |
| --- | --- | --- | --- |
| `content` | string | Yes | 4–500 characters, single line |

The category is re-derived from the new text. Returns `{ "memory": { … } }`.

`400` if `content` is missing or outside the range. `404` if the memory does not exist for this
account.

### `DELETE /api/memories/:id` — Session

Returns `{ "ok": true }`. `404` if the memory does not exist for this account.

> [!NOTE]
> This endpoint edits and deletes memories. JARVIS creates them during a conversation — there is no
> endpoint to create one directly.

---

## Web {#web}

Session-only.

### `GET /api/web` — Session

Web search status and lookup history.

```json
{
  "searchConfigured": true,
  "enabled": true,
  "lookups": [
    { "id": "…", "query": "Node.js release notes", "status": "ok", "resultCount": 5, "error": null, "conversationId": "0f3a…", "createdAt": "…" }
  ]
}
```

`status` is `ok` or `error`. `searchConfigured` reports whether search is available on the
deployment — it never reveals configuration.

### `DELETE /api/web` — Session

Clears the lookup history. Returns `{ "ok": true, "cleared": 12 }`.

The preference and stored conversations are untouched.

---

## Keys {#keys}

Session-only. A key object is
`{ id, name, prefix, createdAt, lastUsedAt, revokedAt }`.

### `GET /api/keys` — Session

Returns `{ "keys": [ … ] }`. Key material is never included.

### `POST /api/keys` — Session

Create a key.

| Body | Type | Required | Notes |
| --- | --- | --- | --- |
| `name` | string | No | Up to 40 characters. Defaults to `Default key` |

```json
{
  "key": "jrv_<your-new-key>",
  "id": "…",
  "name": "ci",
  "prefix": "jrv_XXXXXX",
  "createdAt": "…"
}
```

> [!WARNING]
> `key` is returned **only** in this response. It is stored as a hash and cannot be retrieved
> again. Store it immediately; if you lose it, revoke the key and create another.

`429` after 10 creations per minute.

### `DELETE /api/keys/:id` — Session

Revokes a key. Returns `{ "ok": true }`. Takes effect on that key's next request.

`404` if the key does not exist for this account.

---

## Account {#account}

### `GET /api/auth/session` — None

The current session.

```json
{ "user": { "id": "…", "email": "you@domain.com", "displayName": "Sam", "preferredLanguage": "auto", "memoryEnabled": true, "webSearchEnabled": true } }
```

Returns `{ "user": null }` when signed out. Returns `403` if the account is restricted and `503` if
access cannot be verified.

### `POST /api/auth/logout` — Session

Ends the current session. Returns `{ "ok": true }`.

### `POST /api/auth/register` — None

| Body | Type | Required | Notes |
| --- | --- | --- | --- |
| `email` | string | Yes | Valid address, up to 254 characters |
| `password` | string | Yes | 8–128 characters |
| `confirmPassword` | string | Yes | Must match `password` |
| `displayName` | string | No | Up to 64 characters |

Returns `201` with `{ "user": { "id": "…", "email": "…" } }` and sets a session cookie.

`409` if the address already has an account. `403` if the address is restricted.

### `POST /api/auth/login` — None

| Body | Type | Required |
| --- | --- | --- |
| `email` | string | Yes |
| `password` | string | Yes |

Returns `{ "user": { "id": "…", "email": "…" } }` and sets a session cookie.

`401` with the same message for an unknown address and a wrong password. `403` if the account is
restricted.

### `POST /api/auth/forgot` — None

| Body | Type | Required |
| --- | --- | --- |
| `email` | string | Yes |

Always returns `{ "message": "If an account exists for that address, a reset link is on its way." }`
— whether or not the address is registered. `503` if the email could not be handed off for delivery.

### `POST /api/auth/reset` — None

| Body | Type | Required | Notes |
| --- | --- | --- | --- |
| `token` | string | Yes | From the reset email |
| `password` | string | Yes | 8–128 characters |
| `confirmPassword` | string | Yes | Must match |

Returns `{ "ok": true }`. On success every session for the account is ended and outstanding reset
links are invalidated.

`400` if the token is unknown, already used, or expired (30 minutes).

### `PATCH /api/auth/profile` — Session

Update your own preferences. Send any subset of fields.

| Field | Type | Notes |
| --- | --- | --- |
| `displayName` | string | Up to 64 characters |
| `preferredLanguage` | string | `auto`, `en`, `de`, or `it` |
| `memoryEnabled` | boolean | Store and use memories |
| `webSearchEnabled` | boolean | Allow web searches |
| `displayNameChangePromptDisabled` | boolean | Stop offering name-change prompts |

Returns the updated `{ "user": { … } }`.

`400` if a field has an invalid value or nothing valid was sent.

> [!NOTE]
> Email address changes are not supported. The address on the account is fixed at sign-up.

### `GET /api/auth/oauth/:provider` — None

Starts a provider sign-in. `:provider` is `google` or `github`. Responds with a redirect to the
provider, then returns to `/api/auth/oauth/:provider/callback`.

Errors are returned as a redirect back to the sign-in page with a code: `config`, `state`,
`denied`, `provider`, `email`, or `server`. See
[Sign-in errors](/jarvis/account/sign-in/#what-the-buttons-can-look-like-after-a-redirect).

---

## Health {#health}

### `GET /api/health` — None

Service status. See [Health](/jarvis/api/health/).

---

## Related

- [API Overview](/jarvis/api/) — base URL and conventions
- [Authentication](/jarvis/api/authentication/) — credentials, endpoint access, and rate limits
- [Health](/jarvis/api/health/) — the status endpoint in detail
- [SpaceKeep API Reference](/developers/api-reference/) — the SpaceKeep platform API
