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

> ## Agent Instructions
> The PartFinder API is served at https://api.partfinder.dev. Authenticate with `Authorization: Bearer $PARTFINDER_API_KEY`; create an account at https://console.partfinder.dev/sign-up and pick pay as you go or a monthly or yearly plan (see https://partfinder.dev/pricing), then create a key. Pay as you go starts with 100 free credits, once per person and per company.
> Install the PartFinder agent skill with `npx skills add https://docs.partfinder.dev` (skill file: https://docs.partfinder.dev/skill.md).
> The OpenAPI document at https://api.partfinder.dev/v1/openapi.json is the source of truth for request and response schemas.
> What each call costs and current plans: https://api.partfinder.dev/llms.txt.

# Error responses and status codes in the PartFinder API

> PartFinder returns machine-readable JSON error codes alongside standard HTTP status codes, with a sentence you can relay. Learn what each code means and how to handle errors in code.

Every error response from the PartFinder API has the same structure: a JSON object with an `error`
code, and usually a `summary` sentence written to be relayed as-is. Read the HTTP status code to
understand the category of failure, then branch on `error` for the exact cause.

## Error response format

```json theme={null}
{
  "error": "part_not_found",
  "summary": "\"TPS5433\" was not found at any supplier — it looks like a partial part number. Did you mean TPS54331, TPS54332, TPS54331D?",
  "reason": "No supplier offer matched this part number exactly. Coverage is broad but not universal — a part sold only direct by its manufacturer can also be absent.",
  "did_you_mean": ["TPS54331", "TPS54332", "TPS54331D"]
}
```

| Field | Presence | What it is |
| - | - | - |
| `error` | always | One of the documented codes below. Branch on this field. |
| `summary` | when there is something useful to say | One sentence you can relay to a user as-is, the same as `summary` on a successful response. |
| `reason` | when there is a specific detail | The specific detail behind the error, for a developer reading the body. |
| `did_you_mean` | on some `404` and `422` answers | Real part numbers to retry with. Every entry is a real part number, never a guess. |

## HTTP status codes

| Status | Meaning |
| - | - |
| `200 OK` | The request succeeded. |
| `201 Created` | A request for quote, a message in one, a webhook endpoint or a customer key was created. |
| `202 Accepted` | The BOM, the search or the comparison was accepted and has started; poll `GET /v1/bom/{id}`, `GET /v1/search/{id}` or `GET /v1/compare/{id}`. |
| `400 Bad Request` | The request was malformed: a required parameter missing, a body that is not valid JSON, a line without `mpn`, a `country` that is not a two-letter country code. |
| `401 Unauthorized` | The API key is missing, unknown, revoked or expired. |
| `402 Payment Required` | The call costs credits and your organization's billing does not cover it right now. Nothing was run or charged. |
| `403 Forbidden` | A customer's key called an endpoint only your organization's own keys may call, or an AI app you signed in with lacks the permission the call needs or was disconnected in the console. |
| `404 Not Found` | No supplier lists that part number exactly, or no BOM, search, comparison, request for quote, webhook endpoint or key with that id belongs to your organization. |
| `409 Conflict` | A key with that name already exists in your organization, the supplier asked not to receive requests for quote, or the supplier's address bounced. |
| `422 Unprocessable Entity` | The request was structurally valid but could not be processed: an input that is not one specific part, a search query that is not about a part, a part in a comparison that is not one specific part, a BOM over the line limit, an uploaded file with no part-number column, or a message that was not sent. |
| `429 Too Many Requests` | Your organization has used its calls for this minute or this day. Wait the seconds in `Retry-After`, then retry. |
| `502 Bad Gateway` | The report could not be produced, or an email could not be written or sent, right now. Nothing was returned or sent. Temporary; retry shortly. |
| `503 Service Unavailable` | The supplier data needed to confirm a part number is momentarily unavailable, so no report was produced. Temporary; retry shortly. |

## Error codes

| Code | HTTP status | Description |
| - | - | - |
| `authentication_required` | `401` | No Bearer token was sent with the request. |
| `invalid_key` | `401` | The key is unknown, revoked, or expired. |
| `invalid_request` | `400` | A required parameter is missing, a parameter is not valid (such as `country`), or the body could not be read. `reason` names the parameter or the line. |
| `not_a_specific_part` | `422` | The input does not identify one specific part: a question, a category, or a generic specification. When it described a class of parts, `did_you_mean` carries real part numbers. |
| `part_not_found` | `404` | No supplier lists that part number exactly, usually a typo or a partial number. When it looks partial, `did_you_mean` carries the listed part numbers it is a prefix of. |
| `verification_unavailable` | `503` | We could not check whether the part is real, and declined to describe it. Temporary; retry shortly. |
| `generation_failed` | `502` | The report could not be produced, or a BOM analysis, a search or a comparison could not be started. Nothing is stored. |
| `bom_too_large` | `422` | One call carries more lines than the limit; `reason` states the limit and what was sent. |
| `bom_not_found` | `404` | No BOM with that id belongs to your organization. |
| `bom_columns_not_found` | `422` | An uploaded file has no column we can identify as part numbers. `reason` lists the headers seen. |
| `event_not_found` | `404` | No active event has that id. |
| `not_a_parts_query` | `422` | A search query is not about a part. Send a part number, or describe the part by its type and main specifications. Nothing was started. |
| `search_not_found` | `404` | No search with that id belongs to your organization. |
| `compare_not_found` | `404` | No comparison with that id belongs to your organization. |
| `key_not_allowed` | `403` | A key you created for one of your customers called `/v1/keys`; use one of your organization's own keys. |
| `insufficient_scope` | `403` | You signed in through an AI app without the permission this call needs: read to look things up, write to start, send or delete anything. Connect the app again and allow it. |
| `app_disconnected` | `403` | The AI app you signed in with was disconnected in the console. Its calls are refused until it is allowed again there. |
| `key_not_found` | `404` | No live customer key with that id belongs to your organization. |
| `key_name_taken` | `409` | A key with that name already exists in your organization, revoked keys included. Choose another name. |
| `rfq_not_found` | `404` | No request for quote with that id belongs to your organization. |
| `supplier_opted_out` | `409` | The supplier asked not to receive requests for quote from you, or from anyone through PartFinder. Nothing was sent. |
| `rfq_bounced` | `409` | The supplier's address could not receive the request. Send a new request for quote to a working address. |
| `message_refused` | `422` | Your text was not sent; `summary` says what to change before sending again. |
| `send_failed` | `502` | The email could not be sent. Nothing was stored; retry shortly. |
| `webhook_endpoint_not_found` | `404` | No webhook endpoint with that id belongs to your organization. |
| `rate_limited` | `429` | Your organization has used its calls for this minute or this day. The `Retry-After` header gives the seconds to wait. |
| `credits_exhausted` | `402` | Your plan's credits for this month are used up. Nothing was run or charged. Upgrade the plan at `billing_url`, then retry. On an Enterprise contract, the credits for the current contract term are used up; your plan changes only through PartFinder, so ask your organization's administrator to contact [support@partfinder.dev](mailto:support@partfinder.dev). |
| `payment_method_required` | `402` | Your organization is on pay as you go with no payment method. Nothing was run or charged. Add a payment method or choose a plan at `billing_url`, then retry. |
| `payment_failed` | `402` | Pay as you go is paused because the last payment failed. Nothing was run or charged. Update the payment method at `billing_url`, then retry. |
| `usage_limit_reached` | `402` | Your organization, on pay as you go, has reached its limit for unpaid usage. Nothing was run or charged. Calls resume once the current payment goes through; follow it at `billing_url`. |

## Authentication errors

All `401` responses return a JSON body with `error` set to either `authentication_required` (when
no token was sent at all) or `invalid_key` (when a token was sent but is not valid).

```json theme={null}
{
  "error": "invalid_key"
}
```

Unknown keys, revoked keys, and expired keys all return `invalid_key`. This is intentional:
distinguishing between these states in the response would allow an attacker to enumerate whether a
given key exists in the system. If you receive `invalid_key`, verify that you are sending the
correct key and that it has not been revoked in the console.

> [!NOTE]
>
> A revoked key can be accepted for up to a minute: see [Rotating keys](https://docs.partfinder.dev/authentication.md#rotating-keys).

## Rate limits

How many calls your organization can make depends on its plan:

| Plan | Calls a minute | Calls a day |
| - | - | - |
| Pay as you go | 120 | 2,000 |
| Starter | 120 | 2,000 |
| Growth | 300 | 5,000 |
| Scale | 600 | 15,000 |

Every call made with
an API key counts, whatever the endpoint; the OpenAPI document does not. All keys of one
organization share its allowance. A key you created for one of your customers can make as many
calls as your organization, and counts against your organization's allowance too. Minutes and days are counted on the clock in UTC: a new minute
starts on the minute, a new day at 00:00 UTC.

Over either limit, the API answers `429` with `error: "rate_limited"`, a `summary` you can relay,
and a `Retry-After` header with the number of seconds until the limit resets.

```json theme={null}
{
  "error": "rate_limited",
  "summary": "This account has reached its limit of 120 calls a minute. Try again in 23 seconds.",
  "reason": "Rate limit: 120 calls per minute per organization."
}
```

Wait at least `Retry-After` seconds before the next call; retrying sooner is refused again. If
your usage needs a higher allowance, contact us and we will raise it for your organization.

Every response to a call made with a valid key carries three headers so you can slow down before
you reach the limit. That includes errors: a `400`, `402`, `404`, `422`, `502` or `503` counts as a call and
carries them, so retrying failed calls uses up your allowance too. A `401` (the key was not
accepted) and a `413` (the request body was too large) are not counted and carry none.

| Header | Meaning |
| - | - |
| `X-RateLimit-Limit` | Calls allowed in the current window: the minute or the day, whichever has fewer calls left. |
| `X-RateLimit-Remaining` | Calls left in that window. |
| `X-RateLimit-Reset` | Seconds until that window resets. |

Polling a BOM with `GET /v1/bom/{id}` counts too: at one poll every 5 seconds, one BOM uses 12
calls a minute.

## Handling errors in code

Check the HTTP status code first, then read the `error` field for the specific cause. `summary` is
written to be shown to a user or relayed by an agent as-is. The examples below demonstrate a
pattern you can adapt to your integration.

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

import requests


def call_partfinder(url):
    headers = {"Authorization": f"Bearer {os.environ['PARTFINDER_API_KEY']}"}
    response = requests.get(url, headers=headers)

    if response.ok:
        return response.json()

    body = response.json()
    status = response.status_code
    code = body.get("error", "unknown")
    message = body.get("summary", code)

    if status == 401:
        raise ValueError(f"Authentication failed ({code}): check your API key.")
    elif status in (400, 404, 422):
        # Not retryable as sent. `summary` says what to change, and
        # `did_you_mean` (when present) gives real part numbers to retry with.
        raise ValueError(message)
    elif status in (502, 503):
        # Temporary. Retry with backoff.
        raise RuntimeError(f"{message} (retryable)")
    else:
        raise RuntimeError(f"Unexpected status {status}: {code}")


print(call_partfinder("https://api.partfinder.dev/v1/parts?mpn=TPS5433"))
```

```javascript JavaScript theme={null}
async function callPartFinder(url) {
  const response = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.PARTFINDER_API_KEY}` },
  });

  if (response.ok) {
    return response.json();
  }

  const body = await response.json();
  const message = body.summary ?? body.error;

  switch (response.status) {
    case 401:
      throw new Error(`Authentication failed (${body.error}): check your API key.`);
    case 400:
    case 404:
    case 422:
      // Not retryable as sent. `summary` says what to change, and
      // `did_you_mean` (when present) gives real part numbers to retry with.
      throw new Error(message);
    case 502:
    case 503:
      // Temporary. Retry with backoff.
      throw new Error(`${message} (retryable)`);
    default:
      throw new Error(`Unexpected status ${response.status}: ${body.error}`);
  }
}

console.log(await callPartFinder("https://api.partfinder.dev/v1/parts?mpn=TPS5433"));
```

```bash cURL theme={null}
# curl -w prints the HTTP status on a new line after the response body.
# Check the status code, then inspect the "error" field in the JSON body.
curl -s -w "\n%{http_code}" \
  -H "Authorization: Bearer $PARTFINDER_API_KEY" \
  "https://api.partfinder.dev/v1/parts?mpn=TPS5433"

# On error you will receive a body like {"error":"part_not_found","summary":"..."}
# and a non-2xx status code. Pass -f to make curl itself exit non-zero on
# 4xx/5xx, or parse the trailing status line in your shell script.
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.