> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flowsign.app/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Flowsign is one word with a lowercase s.
> The REST API base URL is https://my.flowsign.app and every endpoint lives under /api/v1.
> When answering API questions, cite the HTTP method and endpoint path.
> API access needs the Enterprise plan and an API key with the API access permission.

# Errors and rate limits

> Response envelopes, status codes, validation details and the request limit.

## Envelopes

Successful responses wrap their payload in `data`:

```json theme={null}
{ "data": { "id": "pkg_9f3c2e" } }
```

Errors carry a human-readable `error` and, on `422` validation failures, a `details` map:

```json theme={null}
{
  "error": "Validation failed",
  "details": {
    "recipients.0.email": ["Invalid email"],
    "title": ["Required"]
  }
}
```

Calls that create a resource return `201`. `POST /api/v1/packages/from-template` returns `200` instead when its `externalId` matches a package that already exists. Everything else that succeeds returns `200`, including action calls made with `POST` such as a bulk send or a signing URL.

## Status codes

| Status       | When                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `400`        | The body is not valid JSON (`Invalid JSON body`), or the action is not possible in the resource's current state (for example `Only DRAFT packages can be sent`, a transfer to a user outside the organisation, or a send from a template whose workflow still has an unanswered sender question).                                                                                                                                                                                                                                                                        |
| `401`        | Missing, malformed, unknown, revoked or expired API key, a key whose role no longer has API access, or an `X-Workspace-Id` naming a workspace other than the key's own. The body is always `{ "error": "Unauthorized" }`. See [Authentication](/api-reference/authentication).                                                                                                                                                                                                                                                                                           |
| `402`        | The plan does not include the feature (public API, webhooks, custom fields), the organisation has no credit left to send, or a plan limit such as recipients per package or number of templates would be exceeded. The message says which.                                                                                                                                                                                                                                                                                                                               |
| `403`        | The caller's IP address is outside the organisation's allowlist (`IP address not allowed`), the key's role lacks a permission (`Missing permission: canSendPackages`, or `Missing permission: one of ...` where any of several will do), the role's template access is too low (`Insufficient template access: ... required`), or the route only accepts a signed-in session (`This action requires a signed-in session, not an API key`).                                                                                                                               |
| `404`        | The package, template, document, signing group or webhook endpoint does not exist in the workspace or organisation the call acted in.                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `409`        | The action conflicts with the resource's current state: another member holds the edit lock on the package or template in the app, voiding a package that has already ended, putting on hold a package that is not `IN_PROGRESS`, resuming one that is not `ON_HOLD`, sending a package with no documents or with a document still uploading, reusing an `externalId` that another package already has, or downloading the signed copies or certificate before signing completes. Also returned when creating an API key would take the organisation past 50 active keys. |
| `413`        | A document is too large: `POST /api/v1/templates/{templateId}/documents` with a `fileSize` over the per-document limit, or `GET /api/v1/packages/{packageId}/download` on documents too large to merge into one PDF.                                                                                                                                                                                                                                                                                                                                                     |
| `422`        | The request was well-formed JSON but failed schema validation. `details` is keyed by field path.                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `429`        | Rate limit exceeded. See below.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `500`, `502` | Something went wrong on our side, or a service we depend on (such as document conversion) did not respond. Retry with backoff; if it persists, contact [support@flowsign.app](mailto:support@flowsign.app).                                                                                                                                                                                                                                                                                                                                                              |

## Validation details

`details` keys are dotted paths into the request. Array items are addressed by index, so `recipients.0.email` is the `email` of the first recipient. Query parameters are validated the same way: `GET /api/v1/packages?pageSize=500` returns `422` with `details.pageSize`.

A `422` on a body that references something the target resource does not have (an unknown `roleIndex`, for example) uses the same shape.

## Rate limits

All `/api/v1` routes share one limit: **120 requests per 60-second window**, counted per source IP address. The count is kept per server replica, so the effective ceiling across the fleet can be higher; treat 120 as the floor. Beyond it the API returns:

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 37
Content-Type: application/json

{ "error": "Rate limit exceeded" }
```

`Retry-After` is the number of seconds until the window resets. Wait at least that long before retrying.

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function callWithRetry(url, init, attempts = 3) {
    for (let i = 0; i < attempts; i++) {
      const res = await fetch(url, init);
      if (res.status !== 429) return res;
      const wait = Number(res.headers.get("Retry-After") ?? "1");
      await new Promise((r) => setTimeout(r, wait * 1000));
    }
    throw new Error("Rate limited after retries");
  }
  ```

  ```python Python theme={null}
  import time

  import requests


  def call_with_retry(session, method, url, attempts=3, **kwargs):
      for _ in range(attempts):
          res = session.request(method, url, timeout=30, **kwargs)
          if res.status_code != 429:
              return res
          time.sleep(float(res.headers.get("Retry-After", "1")))
      raise RuntimeError("Rate limited after retries")
  ```

  ```csharp C# theme={null}
  using System.Net;

  static async Task<HttpResponseMessage> CallWithRetryAsync(
      HttpClient http,
      Func<HttpRequestMessage> newRequest,
      int attempts = 3)
  {
      for (var i = 0; i < attempts; i++)
      {
          var res = await http.SendAsync(newRequest());
          if (res.StatusCode != HttpStatusCode.TooManyRequests) return res;

          var wait = res.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(1);
          await Task.Delay(wait);
      }

      throw new HttpRequestException("Rate limited after retries");
  }
  ```
</CodeGroup>

<Tip>
  Prefer [webhooks](/webhooks/overview) over polling `GET /api/v1/packages/{packageId}` to stay well under the limit.
</Tip>
