# Conventions

Response envelope, error format, and cursor pagination.

Every `/v1` response follows the same shape, regardless of resource. Once you've integrated one endpoint, you've integrated all of them.

## Response envelope

Successful responses wrap the payload in `data` and carry pagination hints in `meta`:

    {
      "data": { "status": "ok" },
      "meta": {
        "cursor": null,
        "hasMore": false
      }
    }

- **`data`** — the resource or list of resources. Never null on success.
- **`meta.cursor`** — opaque string; pass it to the next request to continue paginating. `null` when there's no next page.
- **`meta.hasMore`** — `true` if more pages exist.

## Errors

Errors return a structured body regardless of HTTP status:

    {
      "error": {
        "code": "unauthorized",
        "message": "Missing or invalid Bearer token.",
        "details": {}
      }
    }

- **`code`** — stable machine-readable identifier (`"unauthorized"`, `"not_found"`, `"rate_limited"`, `"invalid_argument"`, etc.). Safe to switch on.
- **`message`** — human-readable string. Good for logs; not safe to show verbatim in end-user UIs without translation.
- **`details`** — optional, per-code. For validation errors, contains a field-keyed map of specifics.

Common HTTP statuses:

| Status | Meaning                                                         |
|--------|-----------------------------------------------------------------|
| 200    | Success                                                         |
| 400    | Invalid request (body shape, missing required fields)           |
| 401    | Missing / invalid / revoked token                               |
| 403    | Authenticated but not authorized (User key without permissions) |
| 404    | Resource does not exist, or belongs to another organization     |
| 409    | State conflict (exceeded a limit, duplicate)                    |
| 429    | Rate limit exceeded ([Rate limits](/docs/api/rate-limits))      |
| 5xx    | Server error — treat as retryable with backoff                  |

## Pagination

List endpoints accept two query parameters:

- **`limit`** — integer, default `20`, max `100`. Values above `100` are clamped silently.
- **`cursor`** — opaque string from a previous response's `meta.cursor`. Omit on the first request.

Pagination is forward-only and cursor-based — there are no page numbers. The cursor encodes sort position; it is stable across new records being added.

    GET /v1/clients?limit=50
    GET /v1/clients?limit=50&cursor=eyJpZCI6MTIzfQ

When `meta.hasMore` is `false`, stop paginating.

## Request limits

Text fields are length-bounded server-side. Oversized input is rejected with `400 invalid_argument` (never a `5xx`); the error message names the field that was too long, so you can shorten it and retry. Current caps:

| Field                                             | Limit             |
|---------------------------------------------------|-------------------|
| Note `content` (`POST /clients/{clientId}/notes`) | 50,000 characters |
| Note `subject`                                    | 500 characters    |

Lengths are counted in Unicode characters (code points), not bytes.

## Multi-tenancy

Every request is scoped to the organization that owns the Bearer key. Objects that belong to a different organization return `404` (not `403`) — Salfio does not leak the existence of other orgs' data.

## Related pages

- [Administrator Tools](./administrator-tools.md)
- [Agent Tools](./mcp-external-servers.md)
- [Assign a Slack channel to a client over the API](./guides-assign-slack-channel.md)
- [Authenticated health check](./api-reference-gethealth.md)
- [Authentication](./api-authentication.md)
- [Cards](./cards.md)
- [Changelog](./changelog.md)
- [Changelog](../changelog.md)
- [Connect a workspace](./getting-started-connect-workspace.md)
- [Connect an integration](./getting-started-connect-integration.md)

# Agent Instructions

This portal answers questions programmatically. To receive a synthesized,
source-cited answer instead of crawling page by page, append the `?ask=`
query parameter to any page URL on this site:

    /guides/quickstart?ask=how+do+I+authenticate

Optional parameters:

- `&goal=<what-you-are-trying-to-do>` steers the answer toward your
  objective (e.g. `&goal=write+a+python+client`).
- `&version=<label>` scopes the answer to a mounted version when the
  portal publishes more than one.

The response is `text/markdown`: the answer followed by a `# Sources` list
of the portal pages it was grounded in. Status codes are the contract:

- `200` — the answer; `402` — the portal owner’s plan or answer credits are
  exhausted (surface this to your operator; do NOT retry); `429` — you are
  rate-limited; back off for the `Retry-After` seconds; `503` — the answer
  lane is temporarily unavailable; fall back to crawling the `.md` pages.

For the full corpus map read `llms.txt` at the site root; for the tool
surface (search + page fetch as MCP tools) see `/mcp`.
