# Workspace operations

Five domains that get consulted **together**, and always for the same reason: something is already running in production and you need to know how much it is spending, who is handling it, or where a file ended up. These are not things you read to learn; they are the ones you look for at eleven at night.

## How much Energy you are spending

Energy is debited per execution. If your integration fires Mets, this is what tells you the cost before the invoice surprises you:

```ts
const usage = await met.billing.consumption({ from: '2026-07-01', to: '2026-07-31' });
const execs = await met.billing.executions({ limit: 50 });
```

`consumption` is the aggregate for the period; `executions` is the detail, one row per execution with its cost. When the total doesn't match what you expected, the answer is in the detail: it is almost always a Met calling itself in a loop, or a flow running more times than you thought.

```ts
const balance = await met.billing.subscription();
const recharge = await met.billing.autoRecharge();
```

`autoRecharge` matters more than it looks if your integration is unattended: without automatic recharging, running out of Energy **stops executions**, and that looks like "the API stopped responding" when it is really balance. Check it before blaming the code.

The rest is reading the commercial relationship: `invoices()`, `transactions()`, `plans()`, `addons()` and `myAddons()`. Plus `access()`, which answers what the current plan has enabled — useful to avoid calling an endpoint that will return 403 because of the plan and not because of permissions.

## Workspace files

The Media library holds the workspace's files, with folders of its own:

```ts
const images = await met.assets.list({ mime_prefix: 'image/', limit: 50 });
const uploaded = await met.assets.upload(blob, {
  filename: 'proposal.pdf',
  contentType: 'application/pdf',
  folder_id: folder.id,
});
```

`list` returns an array (not a page with a cursor) and paginates with `limit`/`offset`. Besides `mime_prefix` it filters by `folder_id`, `search` and `source` — and that last one is the one that serves for auditing: it distinguishes what a person uploaded (`upload`) from what came out of an item, from what a Met generated (`generated`) or a flow did (`flow_run`).

In `upload`, `filename` is **required**: it is what determines the object's extension in storage, and without it the file ends up with no recognizable type.

Two things that save time:

**You don't generate the thumbnails.** Storage rescales at the edge, so to show a file small you ask for it already sized instead of downloading the original. It is in [Images](images.html), and the measured difference is 870 KB down to 15 KB.

**`cleanupProvisional`** exists because an upload that starts and doesn't finish leaves the file orphaned. It runs as a simulation by default:

```ts
const preview = await met.assets.cleanupProvisional();       // simulates
await met.assets.cleanupProvisional(true);                   // applies
```

Look at the result before passing `true`. It is not reversible.

## Conversations

A conversation is the thread with a contact. The distinction to be clear about:

```ts
const primary = await met.conversations.main();
const all = await met.conversations.list();
```

`main()` returns the workspace's **main** conversation — the one in the panel, where the team talks to the Met. `list()` returns all of them, including each contact's. If what you were after was a contact's history, the short path is [`met.contacts.messages(id)`](contacts.html#read-conversations), not walking `list()`.

`threadMessages(threadId)` pulls the messages of a derived thread, which is what gets created when someone replies inside a message instead of at the end.

## Reminders

A reminder schedules **a message to the contact** for a future date. That is the first thing to be clear about: **it always gets sent**. There is no "internal note" mode — `note` is a field for the team, not a switch that prevents the send.

```ts
await met.reminders.create({
  contact_id: 42,
  scheduled_for: '2026-08-05T14:00:00Z',
  message: 'Writing to check whether you signed the proposal',
  note: 'Follow-up on the July proposal',   // internal, does not travel
});
```

At the scheduled time, a dispatcher sends it to the contact over their channel. The channel comes from the contact, so there is nothing to choose.

`scheduled_for` goes in ISO 8601 and has to be in the future. **With an offset** (`2026-08-05T09:00:00-05:00`, or the `Z` in the example) it is an exact instant; **without an offset** it is interpreted in the workspace's time zone. Send the offset if you compute it on your server: it is the difference between nine in the morning for the customer and nine in the morning for your process.

It goes with a WhatsApp template and not with free text for the same reason as broadcasts: a reminder fires days after the person's last message, that is, outside the 24-hour window, where only approved templates get through. That is why the workspace needs a **default reminder template** configured (in Settings): without one, `create` answers 400. Your `message` goes in as that template's body.

If you want a template other than the default, pass it explicitly — and then `language` is required:

```ts
await met.reminders.create({
  contact_id: 42,
  scheduled_for: '2026-08-05T14:00:00Z',
  template_name: 'proposal_follow_up',
  language: 'en',
  variables: { '1': 'Ana' },                      // the template's placeholders
});
```

`list({ contactId, status })`, `update` and `cancel` round out the CRUD. `update` moves the date or changes the text, and **only works while it is pending**; `cancel` stops it before it is dispatched. Cancel as soon as the reason stops existing: otherwise the contact gets a message out of context.

## Autopilot: when the Met answers and when a human does

By default the Met handles it. Autopilot is the switch:

```ts
await met.contacts.setAutopilot(42, false);      // let a human take over
await met.contacts.pauseAutopilot(42, 30);       // 30 minutes, then it comes back on its own
await met.contacts.setAutopilot(42, true);       // hand it back to the Met
```

**Use `pause` and not `setAutopilot(false)` for a one-off intervention.** It is the most commonly mistaken decision: switching autopilot off is permanent until somebody switches it back on, and what follows is a contact left with no automatic attention for weeks because nobody remembered. `pause` with minutes resumes on its own; `pause(id, 0)` resumes right away.

`markRead(id)` marks the conversation as read, so your integration doesn't leave the team's panel full of unreads you have already processed.

## Agent groups

These are groups of **people** — the human chat agents — not of Mets. They exist to route and assign conversations to a team instead of to an individual:

```ts
const g = await met.agentGroups.create({ name: 'Technical support' });
await met.agentGroups.addMember(g.id, userId);
const members = await met.agentGroups.members(g.id);
```

`addMember` takes a **`user_id`**, and there is the confusion worth avoiding: if what you wanted was to group Mets, that does not exist as a group — a Met is scoped with its [skills and tools](mets-and-tools.html).

Managing them requires a supervisor or admin role, so a key with the scope but belonging to a user without that role gets a 403. It is permission, not plan.

> Everything in this guide operates on the key's workspace. A **partner** key has its own surface: see the [Partners API](../../partners.html).
