# Org / AWS / SES / plan snapshot

`GET /v1/account`

- Authentication: required (Bearer token)
- Required scope: `org.view`

Returns a composed snapshot of the calling org's onboarding state:
organization metadata, the connected AWS account and live SES send
quota, the production-access request status, the CloudFormation
template version, and the active plan. The response is cached
per-org for 60 seconds (a `Cache-Control: max-age=60` response
header advertises this), so repeated polling does not generate
extra AWS or DB load.

Each top-level field is always present — sub-objects are `null`
when the corresponding milestone has not been reached:

- `aws` and `send_quota` are `null` until the customer has connected
  an AWS account, and also `null` if the live SES `GetAccount` call
  fails (the rest of the response still renders — SES failure
  degrades gracefully, it does not 5xx).
- `production_access` is `null` until the customer has submitted a
  production-access request.
- `cloudformation` is `null` until the customer has applied the
  SendOps CloudFormation stack.

`aws.sandbox` is the live SES sandbox flag (true while SES still has
the AWS account in the default sandbox); this is distinct from
`production_access.status`, which reflects the SendOps-side
request workflow. Use this endpoint to drive onboarding UIs,
detect template drift, and gate features on plan tier.

## Example request

```bash
curl 'https://api.sendops.dev/v1/account' \
  -H "Authorization: Bearer $SENDOPS_API_KEY"
```

## Responses

### 200 — Composed account snapshot

Content type: `application/json`

```json
{
  "org": {
    "id": "00000000-0000-0000-0000-000000000000",
    "name": "string",
    "slug": "string"
  },
  "aws": {
    "account_id": "string",
    "region": "string",
    "sandbox": true
  },
  "send_quota": {
    "max_24_hour": 0,
    "max_send_rate": 0,
    "sent_last_24_hours": 0
  },
  "production_access": {
    "status": "pending",
    "granted_at": "2026-05-17T20:00:00Z"
  },
  "cloudformation": {
    "stack_name": "string",
    "template_version": 0,
    "latest_version": 0,
    "up_to_date": true,
    "drift_status": "string"
  },
  "plan": {
    "tier": "free",
    "retention_days": 0,
    "api_rate_limit_per_minute": 0
  }
}
```

### 401 — Missing, malformed, or unknown API key

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ]
}
```

### 403 — Key lacks the required scope or plan limit violated

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ]
}
```

### 429 — Per-org rate limit exceeded

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ]
}
```

### 500 — Unexpected server-side failure. The `code` is `internal_error`. The
`request_id` field can be quoted to SendOps support to investigate.

Content type: `application/problem+json`

```json
{
  "type": "https://example.com",
  "title": "string",
  "status": 0,
  "detail": "string",
  "code": "invalid_key",
  "request_id": "string",
  "retry_after": 0,
  "retention_days": 0,
  "scope": "string",
  "resource": "string",
  "errors": [
    {
      "field": "string",
      "reason": "string"
    }
  ]
}
```
