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

# Errors

> API error codes and how to resolve them

Every API error returns a consistent JSON structure:

```json theme={null}
{
  "error": {
    "type": "authentication_error",
    "code": "invalid_api_key",
    "message": "Invalid API key provided.",
    "doc_url": "https://www.finta.com/docs/api-reference/errors#invalid_api_key"
  }
}
```

| Field     | Description                                                                                                                                |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`    | The error category: `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `limit_error`, or `api_error` |
| `code`    | A specific, machine-readable error code                                                                                                    |
| `message` | A human-readable description                                                                                                               |
| `doc_url` | A link to this page for the specific error                                                                                                 |
| `param`   | The parameter that caused the error, if applicable                                                                                         |

## 401 vs 403

The API draws a sharp line between authentication and permission:

* **401 Unauthorized** means the bearer credential itself is the problem (missing, malformed, or unrecognized). The fix is to obtain a valid API key.
* **403 Forbidden** means the credential was successfully looked up, but the caller is no longer allowed to access the resource. The fix is to regain access (reactivate the user, restore company access, restore an active subscription), not to rotate the key.

Branch on `error.type`, not the status code alone, to decide between "rotate the key" (`authentication_error`) and "regain access" (`permission_error`).

## Summary

| Code                        | Status | Description                                                                                                     |
| --------------------------- | ------ | --------------------------------------------------------------------------------------------------------------- |
| `invalid_api_key`           | 401    | API key is missing, malformed, or unrecognized                                                                  |
| `user_inactive`             | 403    | The user that owns the key has been deactivated                                                                 |
| `user_removed_from_company` | 403    | The user no longer has access to the company                                                                    |
| `company_closed`            | 403    | The company is closed, closing, or deleted                                                                      |
| `subscription_required`     | 403    | The company does not have an active subscription                                                                |
| `resource_missing`          | 404    | The endpoint path does not exist, or a requested resource is missing, hidden, or unsupported for that operation |
| `parameter_missing`         | 400    | A required parameter was not provided                                                                           |
| `invalid_date`              | 400    | A date query parameter is present but not a valid ISO 8601 date                                                 |
| `invalid_date_range`        | 400    | `start_date` is after `end_date`                                                                                |
| `category_update_failed`    | 422    | A transaction category update was rejected by bookkeeping validation                                            |
| `rate_limit_exceeded`       | 429    | Per-minute burst rate limit exceeded (retryable, with `Retry-After`)                                            |
| `monthly_limit_exceeded`    | 429    | Monthly API call limit exceeded (not retryable in the short term, no `Retry-After`)                             |

***

## invalid\_api\_key

**Type:** `authentication_error` | **Status:** 401

The API key in your `Authorization` header is missing, malformed, or unrecognized. This covers credential-level problems only. If the key was valid but the underlying user, company, or subscription has lost access, you will see a `403` with a `permission_error` instead (see below).

```json theme={null}
{
  "error": {
    "type": "authentication_error",
    "code": "invalid_api_key",
    "message": "Invalid API key provided.",
    "doc_url": "https://www.finta.com/docs/api-reference/errors#invalid_api_key"
  }
}
```

**How to fix:**

* Verify the key starts with `finta_` and is copied in full with no trailing spaces.
* Check that you're passing it as `Authorization: Bearer finta_...` (not as a query parameter or other header).
* If the key was revoked, generate a new one from your Finta dashboard under **Settings > API Keys**. Revoked keys cannot be un-revoked.

***

## user\_inactive

**Type:** `permission_error` | **Status:** 403

The user that owns this API key has been deactivated. The key was valid, but the underlying user account is no longer active.

```json theme={null}
{
  "error": {
    "type": "permission_error",
    "code": "user_inactive",
    "message": "The user associated with this API key is no longer active. Reactivate the user or create a new API key under an active user.",
    "doc_url": "https://www.finta.com/docs/api-reference/errors#user_inactive"
  }
}
```

**How to fix:**

* Reactivate the user from **Settings > Members** at [app.finta.com](https://app.finta.com), or
* Create a new API key under an active user.

***

## user\_removed\_from\_company

**Type:** `permission_error` | **Status:** 403

The user that owns this API key no longer has access to the company the key was created for. API keys belong to people, not companies, so removing the user from the company also revokes their keys.

```json theme={null}
{
  "error": {
    "type": "permission_error",
    "code": "user_removed_from_company",
    "message": "The user associated with this API key no longer has access to this company. Ask a company admin to re-grant access, then create a new API key.",
    "doc_url": "https://www.finta.com/docs/api-reference/errors#user_removed_from_company"
  }
}
```

**How to fix:**

* Ask a company admin to re-grant the user access to the company.
* Once access is restored, create a new API key. The old key cannot be reactivated.

***

## company\_closed

**Type:** `permission_error` | **Status:** 403

The company associated with this API key is closed, closing, or deleted. The API is not available for closed companies.

```json theme={null}
{
  "error": {
    "type": "permission_error",
    "code": "company_closed",
    "message": "This company is closed. The API is not available for closed companies.",
    "doc_url": "https://www.finta.com/docs/api-reference/errors#company_closed"
  }
}
```

**How to fix:**

* Contact Finta support if the company should not be in a closed state.

***

## subscription\_required

**Type:** `permission_error` | **Status:** 403

The company does not have an active subscription. This is returned when the subscription is required (no plan), the trial has ended, or the subscription is closed.

```json theme={null}
{
  "error": {
    "type": "permission_error",
    "code": "subscription_required",
    "message": "This company does not have an active subscription. Visit Settings -> Plans at app.finta.com to resolve, then retry.",
    "doc_url": "https://www.finta.com/docs/api-reference/errors#subscription_required"
  }
}
```

**How to fix:**

* Visit **Settings > Plans** at [app.finta.com](https://app.finta.com) to start or restore a subscription, then retry.

***

## resource\_missing

**Type:** `invalid_request_error` | **Status:** 404

The endpoint path does not exist, or a requested resource is missing, hidden, or unsupported for that operation. For write endpoints, Finta may return `resource_missing` for unsupported resources so callers do not learn whether a hidden or unauthorized object exists.

This one code covers two cases: the endpoint itself does not exist (an unknown, removed, or renamed path), or a specific record was not found. Both return this same envelope as JSON, regardless of the `Accept` header you send. The `message` distinguishes the two, but it is human-readable and may change, so compare the request path against the [API Reference](/docs/api-reference) rather than branching on the message.

Authentication is checked first, so a request to an unknown path with a missing or invalid API key returns [`401 invalid_api_key`](#invalid_api_key), not a 404. A `401` is not proof that the path exists; fix authentication first, then interpret the result.

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "resource_missing",
    "message": "No transaction found for id.",
    "param": "id",
    "doc_url": "https://www.finta.com/docs/api-reference/errors#resource_missing"
  }
}
```

**How to fix:**

* Double-check the endpoint path. All endpoints are listed in the [API Reference](/docs/api-reference).
* If a call that used to work starts returning `resource_missing`, check whether the path was renamed or removed. Report paths use underscores, not hyphens (`/reports/income_statement`, not `/reports/income-statement`).
* Verify you're using the correct base URL: `https://app.finta.com/api/v1`.
* If the endpoint accepts an ID, verify the ID prefix and that the object is visible to the API key owner.
* For transaction categorization, only visible standard transactions and selectable categories are supported.

***

## parameter\_missing

**Type:** `invalid_request_error` | **Status:** 400

A required parameter was not provided. The `param` field in the error response tells you which one.

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_missing",
    "message": "Missing required parameter: start_date.",
    "param": "start_date",
    "doc_url": "https://www.finta.com/docs/api-reference/errors#parameter_missing"
  }
}
```

**How to fix:**

* Check the `param` field in the error response to identify the missing parameter.
* Refer to the endpoint's documentation in the [API Reference](/docs/api-reference) for required parameters.

***

## invalid\_date

**Type:** `invalid_request_error` | **Status:** 400

A date query parameter (`date`, `start_date`, or `end_date`) was present but is not a valid ISO 8601 date. The whole value must parse as a calendar date (`2025-12-31`) or a datetime (`2025-12-31T19:00:00Z`, `2025-12-31T23:59:00-08:00`). Trailing characters (`2025-01-31oops`), locale formats (`12/31/2025`), and loose or partial forms (`Jan 2025`, bare `2025`) are rejected rather than coerced to a default or to a leading date. The `param` field names the offending parameter.

An absent or blank date parameter is not this error: it falls back to the endpoint's documented default.

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_date",
    "message": "The `date` value \"banana\" is not a valid date. Use ISO 8601 format, for example 2025-12-31.",
    "doc_url": "https://www.finta.com/docs/api-reference/errors#invalid_date",
    "param": "date"
  }
}
```

**How to fix:**

* Send dates in strict ISO 8601. `2025-12-31` (date only), `2025-12-31T19:00:00Z` (UTC), and `2025-12-31T23:59:00-08:00` (with offset) are all accepted.
* To accept the default, omit the parameter entirely or send it blank. Do not send a placeholder string.
* Check the parameter name. The balance sheet uses `date`; the income statement and cash flow use `start_date` and `end_date`. An unrecognized name is silently ignored (you get the default, not an error), so a `200` with an unexpected date can mean the name was wrong rather than the value.

***

## invalid\_date\_range

**Type:** `invalid_request_error` | **Status:** 400

On the income statement or cash flow, the `start_date` is after the `end_date`. The `param` field is set to `start_date`.

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_date_range",
    "message": "start_date must be on or before end_date.",
    "doc_url": "https://www.finta.com/docs/api-reference/errors#invalid_date_range",
    "param": "start_date"
  }
}
```

**How to fix:**

* Swap the dates so `start_date` is on or before `end_date`.
* Both must be valid ISO 8601 dates (for example `YYYY-MM-DD`); an unparseable value returns [`invalid_date`](#invalid_date) instead.

***

## category\_update\_failed

**Type:** `invalid_request_error` | **Status:** 422

The transaction category update reached the bookkeeping layer, but bookkeeping validation rejected the change. The message can vary by validation, so branch on `error.code`, not exact message text.

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "category_update_failed",
    "message": "Cannot modify category on the primary split transaction",
    "doc_url": "https://www.finta.com/docs/api-reference/errors#category_update_failed"
  }
}
```

**How to fix:**

* Verify you are categorizing a visible standard transaction, not a transfer or split transaction.
* Verify `category_id` points to a selectable category.
* Surface the response message to a human if bookkeeping rejects a change that looks valid.

***

## rate\_limit\_exceeded

**Type:** `rate_limit_error` | **Status:** 429

You've exceeded 6,000 requests per 60 seconds. Authenticated requests are rate limited per API key. Unauthenticated requests (missing or invalid key) are rate limited to 120 requests per 60 seconds per IP address.

The response includes a `Retry-After` header with the number of seconds to wait, plus the standard `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers.

```json theme={null}
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Retry after 12 seconds.",
    "doc_url": "https://www.finta.com/docs/api-reference/errors#rate_limit_exceeded"
  }
}
```

**How to fix:**

* Honor `Retry-After` as the minimum wait. Do not retry sooner.
* For retries beyond the first, apply exponential backoff with jitter and cap the total number of attempts (e.g. 3 to 5) so you fail fast rather than retrying indefinitely.
* Treat `X-RateLimit-Remaining` as a leading indicator: if it is near zero on a successful response, slow down voluntarily.
* If you are paginating, use larger `limit` values (up to 500) to reduce the number of requests.

See [Usage Limits](/docs/api-reference/usage-limits) for the headers table and full retry guidance.

***

## monthly\_limit\_exceeded

**Type:** `limit_error` | **Status:** 429

Your company has exceeded its monthly API call limit. Each plan has a cap on total API calls per company per calendar month:

| Plan                 | Monthly Limit |
| -------------------- | ------------- |
| Formation (free)     | 10            |
| Startup (\$30/month) | 300           |
| Growth (\$300/month) | 30,000        |

The limit is per company, not per API key. If a company has multiple API keys, they all share the same monthly pool. The count resets at midnight Pacific Time on the 1st of each month.

Blocked requests are not counted toward the limit.

```json theme={null}
{
  "error": {
    "type": "limit_error",
    "code": "monthly_limit_exceeded",
    "message": "Monthly API limit reached. Your plan allows 300 calls per company per month. Resets on April 1, 2026. Upgrade in Settings -> Plans at app.finta.com.",
    "doc_url": "https://www.finta.com/docs/api-reference/errors#monthly_limit_exceeded"
  }
}
```

`Retry-After` is **not** sent in this case, deliberately. The meaningful remediation is to upgrade the plan or wait until the 1st of next month, not to schedule a retry.

<Note>
  The error type is `limit_error`, not `rate_limit_error`. These are different situations: `rate_limit_error` means "slow down and retry in a few seconds." `limit_error` means "you are out of quota for the month." Upgrade your plan or wait for the reset.
</Note>

**How to fix:**

* Branch on `error.code` first to distinguish this from `rate_limit_exceeded`. Naive retry loops that do not check the code will burn quota that is already exhausted.
* Stop retrying. Surface the error to your user with the reset date from the message body.
* Upgrade your plan in **Settings > Plans** at [app.finta.com](https://app.finta.com).
* If you can't upgrade, wait for the limit to reset on the 1st of next month.
* Reduce unnecessary API calls by caching responses and using larger pagination `limit` values.
