---
name: account
description: Check the Sonilo account's available services, rate limits, free-trial allowance, and usage/billing history. Use before calling a paid Sonilo tool to confirm it's available and whether free-trial runs remain, or when the user asks about their Sonilo usage, limits, or billing.
license: MIT
compatibility: "Requires Sonilo through either transport — the MCP server connected, or the `sonilo` CLI installed and signed in — plus credentials: a `sonilo login` sign-in, the hosted OAuth plugin, or SONILO_API_KEY. See the setup-api-key skill."
allowed-tools: Bash, Read, Write, mcp__sonilo__*
---

# Sonilo Account

Two free, read-only tools for checking what a Sonilo account can do and how much it's used. Neither makes a charge.

> **Setup:** See the [setup-api-key](../setup-api-key) skill.

## Transport: MCP or CLI

Pick one at the start of the session and stay on it. Do not mix the two inside
a single job, and do not announce the choice.

1. **Sonilo MCP tools visible in this session** (`get_account_services` and friends) — use them. This is the preferred path: it needs no shell, and it is the only one that survives a very long generation. If a call fails to authenticate — rather than failing on its inputs — this transport is not usable in this session: go to 2 instead of retrying it.
2. **No usable Sonilo MCP tools, but `sonilo account` exits 0** — use the CLI commands below. Same API, same account, same credential file. Probe with `sonilo account`, not `sonilo whoami`: whoami exits 0 even when signed out, so it cannot tell the two states apart.
3. **Neither** — stop and run the [setup-api-key](../setup-api-key) skill. Do not call `api.sonilo.com` with curl to work around it; both transports handle uploads, polling and retries that a bare request does not.

## Quick Start

### MCP tool call (recommended)

```
get_account_services()
```

```
get_usage(days=7)
```

### Python (`pip install sonilo`)

```python
from sonilo import Sonilo

client = Sonilo()  # reads SONILO_API_KEY

services = client.account.services()
usage = client.account.usage(days=7)
```

### JavaScript / TypeScript (`npm install sonilo`)

```ts
import { SoniloClient } from "sonilo";

const client = new SoniloClient(); // reads SONILO_API_KEY

const services = await client.account.services();
const usage = await client.account.usage({ days: 7 });
```

### CLI (`npm install -g sonilo-cli` or `pip install sonilo-cli`)

```bash
sonilo account
sonilo usage --days 7
```

`sonilo account` prints the account JSON to stdout and, when the account has a free-trial allowance, one summary line to stderr (`Free trial: text-to-music 1/2 left, ...`) — so `sonilo account | jq .trial` still sees clean JSON.

### cURL (raw REST API, no MCP host)

```bash
curl "https://api.sonilo.com/v1/account/services" -H "Authorization: Bearer $SONILO_API_KEY"
curl "https://api.sonilo.com/v1/account/usage?days=7" -H "Authorization: Bearer $SONILO_API_KEY"
```

## Tools

| Tool | Description |
|------|-------------|
| `get_account_services()` | Available services, rate/concurrency limits, discount factor, max video upload size, and (if the account has one) free-trial allowance per service. |
| `get_usage(days=30)` | Usage summary and per-day breakdown. `days` is 1–365. |

## `get_account_services` Response Shape

```json
{
  "available_services": [...],
  "rpm_limit": 60,
  "concurrency_limit": 4,
  "discount_factor": 1.0,
  "max_upload_size_mb": 300,
  "trial": {
    "text_to_music": {"granted": 2, "used": 0, "remaining": 2},
    "video_to_music": {"granted": 1, "used": 1, "remaining": 0}
  }
}
```

- The `trial` key is **absent entirely** for accounts with no free-trial allowance — treat that as "bills normally," not as an error.
- A `trial` object that's present but has **no entry for a given service** means that service has no free-trial allowance at all and bills from the first call. `dubbing` shows `granted: 1`, but that run is a **15-second preview** of the first single-language call, not a full translation — see the [auto-dubbing](../auto-dubbing) skill.
- When `trial[service].remaining` is `0`, calling that service fails with a `402 trial_exhausted` error that no retry fixes.

## Workflow Tips

- **Check before you generate.** Before calling any paid tool (`text_to_music`, `video_to_music`, `text_to_sfx`, `video_to_sfx`, `video_to_video_music`, `video_to_video_sfx`, `video_to_sound`, `video_to_video_sound`, `dubbing`, `proofread`, `audio_ducking`), call `get_account_services()` if you're unsure whether the account has free runs left. If `trial[service].remaining` is `0`, tell the user their free trial for that service is spent and that continuing needs a payment method — don't just attempt the call and surface a raw 402.
- **`dubbing`'s free run is a 15-second preview**, not a full video: only the first single-language call without scripts is free, and it translates just the first 15 seconds. Several languages, scripts, and every later call are billed. See the [auto-dubbing](../auto-dubbing) skill's cost warning.
- **`proofread` does have a free-trial allowance — 2 runs** — and, like `dubbing`, bills per language after that. See the [proofread](../proofread) skill.
- **Usage reconciliation:** if a generation timed out or failed, use `get_usage` to confirm whether the backend actually completed (and charged) it, rather than assuming nothing happened.
- Rate-limited (`429`)? `get_account_services()` reports `rpm_limit` and `concurrency_limit` so you can tell the user what ceiling they hit.

## Error Handling

Both tools are simple authenticated GETs — expect `401` for an invalid/rotated key. `get_usage` also validates `days` client-side (1–365) before making the request.
