# Agent conventions

These rules apply to every tool. Each tool’s advertised description also states the rules relevant to it, so an agent sees them at call time, but this page collects them in one place.

## `userErrors` means the write did not happen

A non-empty `userErrors` array is a **successful** tool result (`isError` absent or false) reporting a domain rejection, not a crash. Read each entry’s `field` (an array of path segments, for example `["input","name"]`) and `message`, correct the input, then retry or ask the person.

```json
{
"collection": null,
"userErrors": [
  { "field": ["input", "name"], "message": "has already been taken" }
]
}
```

`isError: true` is reserved for authentication challenges, workspace-authorization misses, argument-schema validation failures, rate-limit throttles, unknown tools, and redacted unexpected server errors (`"Tool execution failed"`).

> **Branching on isError alone misses failed writes**
>
> A mutation that changed nothing returns `isError` false. Always inspect `userErrors` before treating a write as done.

## Retry discipline

On a timeout or transport error where you can’t tell whether the write landed, read first with the tool that verifies it, before retrying. Every write tool’s description names that read tool; a delete is confirmed when the read returns `null`.

Tools annotated `idempotentHint: true` can be retried directly: a duplicate apply converges on the same state.

> **Never blind-retry these**
>
> Every `create*` tool, plus `uploadItemFromUrl` and `refetchEngagementBulk`. A duplicate creates a second entity, a second import, or re-spends credits.

## Destructive tools

The `delete*` tools set `annotations.destructiveHint: true`. Their descriptions state what is destroyed, what survives, and that the delete is irreversible. Confirm with the person, echoing the entity ID back to them, before calling one.

Deleting a saved view or Collection never deletes content: the items survive and lose only that one tag or grouping. No other tool spends money or destroys creator data.

## Async ingest

`uploadItemFromUrl` and `refetchEngagementBulk` are asynchronous. A success payload means the work was enqueued, not that it finished. Poll the verify tool named in the description for the eventual result, and don’t expect fresh data in the write response.

## Concise responses

Every list read tool accepts `responseFormat`, either `"detailed"` (the default) or `"concise"`. Concise trims each item node to the fields an agent needs for listing, filtering, and ranking: ID, `originalUrl`, provider, type, `takenAt`, caption, a minimal `socialProfile`, and `currentEngagement`. On `searchItems` it also keeps `creator { id }`, the join key for creator-level diffs such as differencing a campaign roster against that campaign’s posters; matching on `accountName` is unsafe there because a workspace can hold two creator records sharing a handle.

Prefer concise while you are narrowing a result set, and switch to detailed only when you need the fuller shape. Concise is materially cheaper in context, and the trimmed fields are a strict subset: nothing changes shape between the two modes.

## Enum values

Every enum value is `UPPERCASE`. Item types encode the platform and the format together, which is a common cause of empty result sets — and the value an item reads back as is not always a value you can filter by. [Core concepts](/api/v2/docs/guides/concepts#platform-and-format-are-separate) explains why the two enums differ.

> **itemTypes and provider can contradict each other**
>
> A TikTok post is `TIKTOK`, not `POST`. Combining `provider: TIKTOK` with `itemTypes: [“POST”]` matches nothing. Use one or the other, never a conflicting pair.

## View filters: clone, don’t compose

For content view, Social Profile view, and Creator View create and update, `filters` is an opaque JSON blob that is not validated on write. Prefer cloning an existing view’s `filters` (fetch it with the matching `get*` tool) over composing one from scratch.

> **SP and Creator View filters are not applied on read**
>
> `getSocialProfiles(presetId:)` and `searchCreators(presetId:)` narrow only by the view’s `customAttributeConditions` and sort. The `filters` blob is stored and echoed back, never applied. To build a view that actually filters, use `customAttributeConditions`.

## Auditing

Every write emits exactly one structured audit event, whatever the outcome, carrying the principal identity, the auth strategy, the MCP client name, the result, and any `userErrors`.
