# Dreamy API

Base URL is environment-specific. All authenticated requests use
`Authorization: Bearer <Dreamy key>`. Collection paths end in `/`.
Errors use `{"detail": ...}`; collections use plural resource envelopes and
`meta`; individual records use singular envelopes. OPTIONS `/admin/` exposes
resource/field metadata. Supported list features: equality, `in`, `icontains`,
`sort[]`, `include[]`, `exclude[]`, `page`, and `per_page` (max 10,000).

| Endpoint | Method | Capability | Behavior |
|---|---|---|---|
| `/health` | GET | public | Service and build revision |
| `/auth/magic-link` | POST | public | `{email}`; requests one-use company sign-in |
| `/auth/google` | GET | public | Start Google OIDC sign-in with browser-bound state and PKCE |
| `/auth/google/callback` | GET | public | Validate Google identity and return one-use sign-in handoff |
| `/auth/verify` | POST | public | `{token}`; consumes link, returns session bearer |
| `/admin/` | OPTIONS/GET | read | Resource metadata |
| `/admin/users/me/` | GET | read | Current user |
| `/admin/projects/` | GET/POST | read/write | List/create `{name?, description?, visibility?, icon?}`; creator is owner, private by default |
| `/admin/projects/{id}/` | GET/PATCH/DELETE | read/write | Read/update project configuration; deletion requires owner |
| `/admin/projects/{id}/messages/` | POST | write | `{content}` plus `Idempotency-Key`; returns 202 with message and task |
| `/admin/projects/{id}/source/` | GET | read | `{monorepo: {project, revision, digest, files}}` |
| `/admin/projects/{id}/publish/` | POST | publish | Create/reuse release request for latest revision |
| `/admin/{resource}/` | GET/OPTIONS | read | Resources: messages, tasks, revisions, environments, releases, providers, roles, dashboards, views, users |
| `/admin/{resource}/{id}/` | GET | read | Retrieve record |
| `/admin/providers/` | POST | write | `{name, kind:"agent", config:{adapter,model?,description?}, enabled?}` |
| `/admin/providers/{id}/` | PATCH | write | Update provider configuration |
| `/admin/roles/`, `/admin/dashboards/`, `/admin/views/` | POST/PATCH detail | write | Shared configurable resources |
| `/admin/api_keys/` | GET/POST | manage_keys | Browser session creates/list keys |
| `/admin/api_keys/{id}/` | DELETE | manage_keys | Revoke key |
| `/admin/tasks/{id}/cancel/` | POST | write | Cancel queued/running task; fence worker |
| `/worker/claim` | POST | worker | `{}` (optional `project_ids: UUID[]`, `priorities: [low|normal|high|critical]`, legacy `provider: UUID`) → task, lease token, project, revision, conversation; task null when idle |
| `/worker/tasks/{id}/heartbeat` | POST | worker | `{lease_token}`; extend valid lease |
| `/worker/tasks/{id}/complete` | POST | worker | `{lease_token, content, summary?, project_name?, project_icon?, workspace_version?, blueprint?, files?, error?}` |
| `/ci/releases/{id}/transition` | POST | ci | Record next gate for this key's project |

Automated clients can create a key with `read,write` and optional `project_id`.
Creation of new projects requires an unscoped key. Worker keys have only `worker`;
CI keys have only `ci` and require `project_id`. Project membership and public visibility apply independently of key scopes. Private projects require membership; public projects are read-only for signed-in Dreamy users. Delegated keys cannot mint more keys. `publish` is a separate capability.

Key creation: `{name, scopes: ["read","write"], project_id: null, expires_in_days:30}`.
Omitting the lifetime defaults to 30 days. Use 1–3650 days or explicit `null` for **Never**. `expires_at:null` on listed keys means no expiration. Browser sessions still expire after 12 hours.
The response contains metadata in `api_key` and the one-time plaintext `token`.

To list a project's chat, request
`/admin/messages/?filter{project}=UUID&sort[]=created`. Repeat an uncertain message
submission with the same idempotency key and content; changing the content with
that key returns 409.

An agent's `blueprint` contains exactly `models`, `hooks`, `tasks`, `roles`, `config`.
The four code/permission layers are arrays; config must preserve the shared
foundation and both environments. `files` maps paths to complete text contents,
with allowed roots `models/`, `hooks/`, `tasks/`, `roles/`, `config/`, `tests/`,
and `frontend/overrides/`. Supply blueprint and files together for a new revision.
Messages without code changes can return `content` alone.

CI transitions require:

```json
{
  "event": "tests_passed",
  "revision": "revision-uuid",
  "digest": "revision-sha256",
  "artifact_digest": "64-character-sha256-of-built-artifact",
  "run_url": "https://gitlab.example/dream/project/-/pipelines/123",
  "deployment_url": null
}
```

Then `dev_deployed` (with HTTPS URL), `e2e_passed`, `production_deployed` (with URL).
`failed` terminates an active release. Gates cannot be skipped, revisions cannot
be substituted and the artifact digest cannot change. CI credentials are trusted
attestors; model output and browser requests cannot mark tests passed.

## Project settings and activity

Omit `name` to immediately create a private **Untitled app**. Its first successful
worker completion must supply `project_name` (1–100 characters). The database
identifier stays stable after renaming. An owner membership is created in the same
transaction and `access_role:"owner"` is returned immediately. PATCH `name`
autosaves a user-selected name and clears `name_pending`.

Owner-only membership endpoints: GET/POST/DELETE `/admin/projects/{id}/members/`.
POST `{email,role}` adds or changes `owner`, `editor`, or `viewer`; DELETE `{email}`
removes access. At least one active owner must remain. Pending invitations become
active when that email first signs in. Owners manage visibility and publishing;
editors can chat and edit project configuration; viewers browse only.

Project `work_state` is `queued`, `running`, or `idle`. Compare `work_completed_at`
and `work_seen_at` to detect unseen work. POST `/admin/projects/{id}/seen/` with
`{completed_at: <the timestamp rendered by your client>}` acknowledges that exact
completion for the whole project. A newer completion cannot be cleared accidentally.
Any authorized reader can acknowledge work. Cancelled messages remain in the API
history for audit but the chat UI hides them.

POST `/admin/projects/{id}/cancel/` with `{tasks:[UUID,...]}` atomically cancels the
specified active jobs in that project and fences their worker leases. Editors and
owners can cancel. Other projects' IDs have no effect.

Project icons: PATCH `icon:{type:"icon",name:"database"}` or a previously uploaded
`icon:{type:"image",id:UUID,name:"Example"}`. POST `/admin/images/` with
`{project:UUID,name,data:"data:image/png;base64,..."}` uploads and sets the icon
atomically. The square PNG is bounded to 128 KB and 512 pixels per side; the UI
crops to 128 × 128 before sending it. Bytes are stored in a PostgreSQL **large
object**, with only the OID in `dream_images` and a UUID reference in the project.
Existing embedded icons are migrated automatically.

GET `/admin/images/{id}/` requires a read key and image ownership or permission to
read a project using it; project-scoped keys are restricted to that project.
GET `/admin/recent_images/` returns the user's 24 most recent image references.
Scoped keys cannot browse that library. Replaced icons are retained for reuse.
DELETE `/admin/images/{id}/` allows the uploader to remove an unused image; used
images return 409. A transactional trigger calls `lo_unlink`, avoiding orphaned
large objects. Include large objects in database backups (`pg_dump` includes them
in normal full-database dumps); migrations/runtime need ownership of Dreamy's LOs.
No persistent large-object descriptors or session state cross RDS Proxy requests.

DELETE a project to remove Dreamy's stored chat, jobs, revisions, and membership and
revoke its scoped keys. This does not delete remote Git repositories or deployed
cloud resources.

## Shared agents and repository sync

`pool_worker` keys may process authorized pool work across projects; only emails
configured in `DREAM_POOL_OPERATOR_EMAILS` can create or use them. Personal `worker`
keys can work only on projects where their user is an owner/editor. Pool keys do
not grant generic admin reads. Optional `project_id` restricts both worker types.

Repository connections and worker setup are described in [repositories.md](repositories.md).

See [connecting agents](agents.md) for runnable worker commands and shared-pool setup.

## Agent workspace protocol

GET `/skills/dreamy-agent/SKILL.md` returns the public skill. POST
`/worker/tasks/{id}/workspace` with `{lease_token,action,...}` reads or edits that
session's private draft. Actions: read, search, edit, diff, export, validate,
checks, checkpoint, history, restore. See [the exact request shapes](../skills/dreamy-agent/SKILL.md).
Edits/checks/checkpoint/restore require `version`; final completion requires `workspace_version`.
Success creates one immutable revision and returns `sync_job`; it does not imply
the remote commit has already been pushed. Workspace validation is schema/path
validation only; agent test reports are not CI evidence.

Completion accepts `summary`: one or two short sentences, at most 360 characters,
stating the outcome and any blocker. Keep the full reply in `content`. The UI shows
the summary first; its Read more icon replaces that text with the full reply and
can collapse it again. Older messages without a summary use a shortened preview.

Worker leases last 120 seconds. Renew every 30 seconds while working. Expired
leases are unclaimed and requeued with higher priority (capped at critical).
Saved files, checks, and checkpoints transfer to the replacement worker. Its
claim includes `resume` and recent `activity`; the old lease cannot edit or finish.

## Agent pairing

`POST /admin/agent-pairings/` with `{ "key_id": "EXISTING_KEY_UUID" }` requires an
unscoped `manage_keys` session owned by the same user as a live worker key.
It returns a single-use `code` and `expires_in:300`; copying again replaces the
previous unredeemed code. `POST /agent/connect/redeem` with `{ "code": "..." }`
returns a reusable session credential and its scope once. Both endpoints return `Cache-Control:
no-store`. Revoking the underlying key immediately prevents pairing/agent access.
The agent must use the API URL supplied in its setup prompt; the public
`https://dreamy.so/agent/skill.md` host serves only Markdown.

### Reusable agent sessions and priority

POST `/admin/agent-pairings/` with `{ "key_id": "EXISTING_KEY_UUID" }` from the
key owner's unscoped manage-keys session. Active worker/pool-worker keys support
repeated pairing, including after prior codes were consumed. Legacy `{ "key":
"PLAINTEXT_KEY" }` remains supported. Never send both fields. Each new code replaces
unredeemed codes for the same key, but preserves already-connected sessions.

POST `/agent/connect/redeem` returns `key`, `key_id`, `scopes`, `project_id`, and
`skill_url`. ID-based pairing creates a session credential whose digest is stored
against the parent key. Authentication checks the parent's current expiry,
revocation, user activity and permissions each time. Only pairing codes expire
after five minutes; session credentials remain reusable for the parent's lifetime.

Message creation accepts `priority`: `low`, `normal` (default), `high`, or `critical`.
`POST /worker/claim` accepts nonempty `project_ids` (up to 100 UUIDs) and `priorities`
(up to four levels). Omit filters for all permitted work. Filters intersect key
access. Highest priority is chosen across eligible project queue heads; messages
within one project never bypass earlier queued messages. The response task includes
its priority. Loop clients honor `retry_after` while idle and retain credentials.

## Agent activity

Workspace operations, successful claims, failed workspace calls with a valid lease,
and completion automatically create sanitized activity events. Local execution
and brief progress summaries can be reported with POST
`/worker/tasks/{id}/activity`: `{lease_token,event_id,kind,status,title,summary}`.
Use a UUID event_id and reuse it when changing a step from running to completed or
failed. Kind is progress/tool; titles are limited to 120 characters and summaries
to 2000. Never report credentials or private chain-of-thought.

Project viewers can GET `/admin/projects/{id}/activity/` for each run's latest event
and count, and `/admin/tasks/{id}/activity/?after=0` for a run's chronological chain.
History returns 200 events per page and `next_cursor` when more remain.
