---
slug: agents/api-account
title: Account and usage API
kind: reference
surface: agents
summary: REST reference for Aetherfy account-level control-plane routes — reading the authenticated identity and its live plan limits, setting the account home region on single-region plans, reading monthly compute usage and per-agent lifecycle events, and connecting a GitHub account.
sources:
  - aetherfy-control-plane:api/routes/auth.py
  - aetherfy-control-plane:api/routes/usage.py
  - aetherfy-control-plane:api/routes/regions.py
  - aetherfy-control-plane:api/routes/github.py
  - aetherfy-control-plane:shared/error_codes.py
---

# Aetherfy account and usage API

Base URL `https://agents.aetherfy.com/api/v1`, bearer authentication, and the shared
error envelope are described on the [Agent REST API index](/agents/api).

## Reading the authenticated Aetherfy identity

`GET /api/v1/auth/me` → 200

```bash
curl -s https://agents.aetherfy.com/api/v1/auth/me \
  -H "Authorization: Bearer $AETHERFY_API_KEY"
```

```json
{
  "user_id": "2f9c6b18-0a53-47e2-9d81-4b7c0e35a6f2",
  "email": "team@acme.example",
  "tier": "performance",
  "subscription_status": "active",
  "is_test": false,
  "limits": {"requests_per_minute": 2000, "max_regions": 3, "…": "…"}
}
```

This is the cheapest way to verify a key works, and the only route that answers "which
environment am I in" — `is_test` is `true` for an `afy_test_` key and `false` for an
`afy_live_` one. It is derived from the key itself, not from anything you send.

`limits` is the plan's live limit object. Read your rate budget from
`limits.requests_per_minute` rather than hardcoding a plan's number: it is the value
the limiter actually enforces for this account, so it stays correct across plan
changes. On Enterprise it is `null`, meaning unlimited.

This route needs only a valid key — it does not touch your resources, so it answers
even when everything else is refusing.

## Setting the Aetherfy account home region

`PATCH /api/v1/users/me/home-region` → 200

```bash
curl -s -X PATCH https://agents.aetherfy.com/api/v1/users/me/home-region \
  -H "Authorization: Bearer $AETHERFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"region":"fra"}'
```

This route exists for **single-region plans only**. Free and Starter accounts run in
one region, and this is how you choose it explicitly instead of letting your first
resource decide. On Performance and Enterprise there is nothing account-level to set —
placement is per resource — and the call is refused with
`HOME_REGION_NOT_APPLICABLE`.

Setting it is a race the first writer wins: whichever happens first, this call or your
first region-bearing resource, establishes the region. Both are idempotent afterwards,
so setting the same value again is a 200 that changes nothing.

**Changing it once resources exist is refused**, with 422
`STARTER_REGION_CONSISTENCY`. Moving live data and machines between regions is a
migration, not a toggle. Before you have any resources, changing your mind is free.

| Code | HTTP | Meaning |
|---|---|---|
| `INVALID_REGION` | 400 | Not a region Aetherfy runs in |
| `HOME_REGION_NOT_APPLICABLE` | 400 | Your plan is multi-region; place per resource instead |
| `STARTER_REGION_CONSISTENCY` | 422 | Region-bearing resources already exist |
| `USER_NOT_FOUND` | 404 | |

See [Regions and replication](/platform/regions).

## Reading Aetherfy compute usage

`GET /api/v1/usage/summary` → 200

```bash
curl -s "https://agents.aetherfy.com/api/v1/usage/summary?year=2026&month=8" \
  -H "Authorization: Bearer $AETHERFY_API_KEY"
```

Both `year` and `month` are optional and default to the current month.

```json
{
  "year": 2026,
  "month": 8,
  "total_compute_ms": 184203000,
  "total_compute_seconds": 184203.0,
  "free_tier_seconds": 0,
  "billable_seconds": 184203.0,
  "total_cost_usd": "12.00",
  "agents": [
    {
      "agent_id": "6f1c2b7e-0a2d-4f8e-9c31-2b0d5a7e4411",
      "agent_name": "reporter",
      "compute_ms": 184203000,
      "compute_seconds": 184203.0,
      "cost_usd": "12.00",
      "billing_tier": "performance"
    }
  ]
}
```

Money is a **string**, not a float — `"12.00"`, not `12.0`. Parse it as a decimal.

This is compute time only. It is not your invoice: the plan's flat fee, always-on
add-ons and agent-region charges are not in it. For the full picture see
[Billing and spend caps](/platform/billing) and the
[usage meter](https://app.aetherfy.com/dashboard/billing).

## Reading Aetherfy agent lifecycle events

`GET /api/v1/usage/agent/{agent_id}/events` → 200

Machine start and stop events for one agent — the raw material behind its compute
time, useful for reconciling a surprising figure. Takes `?limit` and, unlike most
routes, requires the agent's **UUID** rather than accepting a name.

Returns 404 `AGENT_NOT_FOUND` for an unknown agent or one belonging to another
account.

## Connecting a GitHub account to Aetherfy

Push-to-deploy needs two steps: connect the **account** once, here, then link each
agent to a repository with the routes on [Deployments](/agents/api-deployments).

`GET /api/v1/auth/github/status` → 200

```bash
curl -s https://agents.aetherfy.com/api/v1/auth/github/status \
  -H "Authorization: Bearer $AETHERFY_API_KEY"
```

```json
{"connected": true, "installation_id": 12345678, "connected_at": "2026-08-19T09:00:00Z"}
```

`GET /api/v1/auth/github` begins the connection. It is **not a JSON endpoint** — it
answers a 302 redirect to GitHub's app-installation page, so it belongs in a browser,
not in a script. There is no way to complete an installation from the API alone: a
human has to approve it on GitHub, and GitHub redirects back to Aetherfy afterwards.
Returns 501 `GITHUB_APP_NOT_CONFIGURED` where the integration is unavailable.

`DELETE /api/v1/auth/github` → 204 disconnects the account. Agent links stop
deploying.

The callback and webhook routes that complete this flow — `/api/v1/auth/github/callback`
and `/api/v1/webhooks/github` — are called by GitHub, not by you, and are
authenticated by signature rather than by your API key.

See [GitHub integration](/agents/github).
