# Running the account

What the account spends, what it owes an answer to, and how to stop it.
Per-resource facts are in [VMs](vm.md) and [email](email.md).

Conventions every call below obeys — auth, errors, idempotency — are in
[API conventions](api.md).

## Projects

Everything you run — VMs, volumes, email domains and credentials, usage,
tickets, compliance cases — lives in a project. Signup creates the account's
first project; none is special.

A `write` or `read` key belongs to the project it was minted into and reaches
nothing else; it needs no header. An `admin` key belongs to the whole account
and has no project of its own: every call to VMs, volumes, email, usage,
support or compliance names the project it is about with a header,

```
AGIdock-Project: proj_...
```

and without one is refused with `400 project_required` — nothing is guessed.
Account-level calls (account, keys, projects, billing, images) take no header.
Naming a project the account does not hold is `404 project_not_found`; a
`write` or `read` key naming any project but its own is
`403 key_project_mismatch`.

```
GET  /v1/projects                  every project, archived ones included
POST /v1/projects                  {"name": ...} create one      (admin key)
PUT  /v1/projects/{id}             {"name": ...} rename           (admin key)
POST /v1/projects/{id}/archive     revoke its keys, refuse new    (admin key)
POST /v1/projects/{id}/unarchive   accept new keys again          (admin key)
```

To mint a `write` or `read` key into a project, name the project in the
`AGIdock-Project` header on `POST /v1/account/keys`.

Archiving revokes every key in the project and refuses new ones, and that is
all it does: the project's VMs keep running and billing. An admin key still
reaches an archived project through the header, so stop what it holds if you
mean it to stop. Unarchiving does not bring the revoked keys back.

The list endpoints answer for one project at a time. The balance and the
ledger are the account's, but usage, quotas, tickets and compliance cases are
per project — an admin key watching the whole account asks each project.

## Usage & billing introspection

- `GET /v1/usage?group_by=meter` — your metered consumption (hourly grain).
  `from`/`to` are RFC3339 and select every hour the window overlaps, so a
  `from` inside the current hour returns that hour rather than nothing; the
  response echoes the window it used. The hour in progress is included and
  flagged `provisional` until it closes (15 min after it ends).
- `GET /v1/billing/balance` — what is left, split grant vs paid, plus a burn
  rate and a runway estimate computed from it. That estimate is the one money
  number worth polling; responses also carry an `X-Credits-Low` header once
  the balance is close enough to the cutoff to act on.
- `GET /v1/billing/ledger` — every debit links to
  the usage that caused it. Usage is debited hourly, so the ledger is mostly
  `debit` entries: `?exclude=debit` leaves money in and out (top-ups, refills,
  grants, expiries, corrections) and `?kind=topup,adjustment` asks for kinds by
  name. Poll the filtered view when you want to know whether a payment landed;
  the unfiltered one is the audit trail.
- `GET /v1/billing/prices` — the live price book: every meter once, at the
  price charging now, under one `effective_from` for the whole book. Nothing
  to resolve — `prices` is what you will be billed at today. An announced
  change comes back under `scheduled`, grouped by the day it takes effect, at
  least 30 days ahead of it. The route is public — no key, no account —
  because price is the pitch, and it is the only published copy: the pricing
  page on the site reads this same endpoint.

Hours where the platform cannot confirm your VM was up are not billed — gaps
resolve in your favor.

Adding credit is your human's, always: `POST /v1/billing/topup` and
`PUT /v1/billing/refill` answer `403 human_required`. `GET /v1/billing/refill`
*is* yours, and it reports the automatic ceiling you are working under.
What happens when the balance reaches zero is under `credit_cutoff` in
[the error reference](api.md#errors-rfc-7807-problem-json).

## Default quotas

Three totals are capped per project, and nothing else is:

| Quota | Default |
|-------|---------|
| RAM across all VMs | 35 GB |
| Root disk across all VMs | 5120 GB |
| Volume storage | 10240 GB |

VM count, vCPU and IPv4 addresses are **uncapped**: the RAM cap bounds them.

`403 quota_exceeded` states the quota, your usage, what you asked for and the
cap, so you need not know the numbers before you hit one. Ask for a raise with
a ticket naming the numbers you want.

Outside these caps, what stops you is your credit balance, or
`409 ip_pool_exhausted` — the address pool is empty right now, which is not a
limit of yours.

Email is limited separately, by a daily recipient count that rises with clean
sending history: [email](email.md).

## Compliance cases — check these, they have deadlines

Abuse and DMCA complaints against your account are opened as cases at
`GET /v1/compliance/cases`. Opening one also mails your human, as does every
reply from our side; that mail is a heads-up to a mailbox you do not hold, and
the complaint, the clock and the thread are on the API. Poll it on the same
schedule you check anything else that can stop your machines; an empty list is
the normal answer.

A case carries `id`, `type` (`spam`, `ddos`, `dmca`, `fraud`), `body` (the
complaint as received, or what we are doing and why), `deadline`, `messages`,
`state` (`open`, `deadline-exceeded`, `closed`), `waiting_on` (`customer` or
`admin`) and timestamps. `waiting_on: customer` is work you have to do or hand
to your human, and `deadline` says by when: **miss it and enforcement
escalates** — content takedown, or your VMs stopped. Nothing answers for you:
if nobody replies, the deadline passes.

**Act before you answer.** Answering without acting is what turns a notice
into enforcement: take the named content down, or stop whatever produced the
signal, and then say what you did and when. Replying starts a conversation —
it does not stop the clock, and no endpoint extends a deadline.

Answer with `POST /v1/compliance/cases/{id}/response {"response": "..."}`.
`messages` is the thread from both sides and it appends: a second reply adds to
the record, it never replaces the first — so a short acknowledgement now and a
fuller answer once you have investigated is better than silence. A closed case
takes no more replies. **There is no counter-notice endpoint**: if a DMCA
claim is wrong, say so in the response with your basis for it, and the thread
is the record.

Every enforcement action has a case, so nothing is done to the account that
you cannot read and answer here — including while you are credit-capped or
suspended, where both compliance calls keep working. The deadline runs in
those states too.

## Support & feedback tickets

A human reads every ticket, and the endpoint works while you are credit-capped
and while the account is suspended: use it for billing disputes and appeals.

```
POST /v1/support/tickets  {"type":"bug"|"billing"|"feedback"|"other",
                           "subject":..., "body":...,
                           "ref": optional resource id}
GET  /v1/support/tickets                       list yours and their states
GET  /v1/support/tickets/{id}                  open -> answered -> closed
POST /v1/support/tickets/{id}/messages         reply in the thread
POST /v1/support/tickets/{id}/close            close it yourself when done
```

`type` routes the ticket; it does not set its priority. `bug` for something
behaving wrongly, `billing` for a charge you dispute, `other` for anything
else that needs an answer.

**`feedback` is the one that needs no problem.** Suggestions, an endpoint you
expected and did not find, a limit that is the wrong shape for the way you
actually work, a guide that led you astray — file it. This is the only channel
for it: there is no forum and no issue tracker, and the addresses on the legal
pages take legal notice and third-party abuse reports, not support. File it
yourself rather than routing it through your human. Nobody may reply, so close
your own feedback tickets — an unanswered one stays `open` and holds a slot
against the cap below.

`ref` is a resource id the ticket is about — a `vm_...`, or a ledger entry id
for a billing dispute. Subject ≤ 200 chars, body ≤ 64 KiB, text only. Max 5
open tickets per project (`409 ticket_cap_reached` — append to an existing one
rather than opening another).

Poll `GET /v1/support/tickets/{id}` for the answer; `answered` auto-closes
after 14 days of silence. The account's human gets a heads-up email when we
reply, but the thread lives in the API and is yours to continue. Quota raises
are a freeform ticket with the numbers you want.

## Emergency stop

`POST /v1/account/freeze` suspends the account immediately — every project in
it, so it takes an admin key (`403 admin_key_required` otherwise). Within 60
seconds every API key on the account — including the one that called it — answers
`403 account_suspended`, on every route but the four that make the freeze
recoverable: `POST /v1/account/unfreeze`, `GET /v1/account`, everything under
`/v1/compliance`, and everything under `/v1/support`.

Nothing is deleted, and running VMs keep running: a freeze is a brake on the
API, not on the machines.

**An admin key can throw it; only your human can release it.** Nothing
challenges the key on the way in, and nothing lets it back out: `POST /v1/account/unfreeze` is
human-only (`403 human_required`) and takes a current authenticator code in the
body on top. A code read out to you does not help — the call refuses your key
before it looks at the code.

Your human has the same freeze in the dashboard, alongside a "stop all VMs"
control that stops the machines as well.
