Get startedDashboard

Errors

Look up every error status and code the API returns, what triggers it and how to fix the request.

View llms.txt

The API reports a failed request with an HTTP status code and a JSON body. Read the status first, then the code field. This page lists every error the API returns, with its body. Quotas and 429 retry timing are covered in more depth on Rate limits.

Error body

Every error body from the data endpoints and /v1/status has error, code, status and detail:

response.json
{
  "error": "bad_request",
  "code": "bad_request",
  "status": 400,
  "detail": "Invalid start date format (YYYY-MM-DD)"
}

code is the error code; branch on it. error holds the same code, except on the 403, the page-limit 400 and the request-limit and Temporarily blocked 429 responses, where it is a short sentence. The 401, 403, page-limit 400 and request-limit and Temporarily blocked 429 bodies also carry message. A 409 adds candidates and hint, and the request-limit 429 bodies add retry_after_seconds. Branch on the HTTP status code, then on code.

fieldtypeunitnullabledescription
errorstrnonenoThe error code. On 403, on the request-limit and Temporarily blocked 429 responses and on the page-limit 400 it is a short sentence.
codestrnonenoError code, such as bad_request, not_found or burst_limit_reached. Present on every error.
statusintnonenoThe HTTP status code repeated in the body. Present on every error.
detailstrnonenoWhat was wrong with the request. Present on every error.
messagestrnonenoPresent on 401, on 403, on the page-limit 400 and on the request-limit and Temporarily blocked 429 responses. It holds the same text as detail, except on Temporarily blocked, where it reads Contact hello@xfinlink.com. On 401 it names the cause.
candidatesobjectnoneno409 only. Maps each ambiguous ticker to the entities it names.
hintstrnoneno409 only. The value is pass entity_id=.
retry_after_secondsintsecondsnoRequest-limit 429 only. Seconds until the limit resets.

All errors

statuserror codewhen it happenshow to fix
400bad_requestA parameter is invalid: a malformed or impossible date, a date outside the endpoint's range, an end before start, a value outside the accepted list, a limit or offset that is not a whole number in range, an invalid entity_id or cursor, no valid name in fields, an unknown index, both a ticker and entity_id, or more tickers than your plan allows in one request.Read detail, which names the parameter and the accepted values. Correct the request and send it again.
400pagination_limit_reachedYou followed a cursor past your plan's page limit: 10 pages on Free, 50 on Pro, 100 on Max, 200 on Redistribution. error reads Pagination limit reached.Narrow the query with start, end, quarter or filters, or split the ticker list. See Pagination.
401unauthorizedAn endpoint that needs a key got none it accepts: no key, a key under another header or parameter name, an incomplete key, a revoked key or an unrecognised key. On resolve and search, a key sent in X-API-Key or api_key that is incomplete, revoked or unrecognised, or a revoked or unrecognised key sent as Authorization: Bearer. Any unknown /v1/ path without a key also returns 401.Read detail, which names the cause, then send your current key in the X-API-Key header. See Authentication.
402upgrade_requiredA Free key called insider transactions, institutional holdings, manager search or manager holdings. A Free or Pro key called /v1/changes, or a Free or Pro account tried to add, change, test or resend a webhook.Use a Pro or higher key. The change feed and webhooks need Max or Redistribution. See Plans and limits.
403account_disabledThe account behind the key is disabled. error reads Account temporarily disabled. Contact hello@xfinlink.com.Contact hello@xfinlink.com.
404not_foundNo entity matches the ticker or entity_id; the ticker's issuer has no listed security on the requested date; the manager id does not exist; or the path is not an endpoint (with a valid key).Check the ticker with Search or Resolve. For an issuer with no listed security, pass the entity_id named in detail. Check the path.
405method_not_allowedThe request used a method other than GET and OPTIONS on a data endpoint or on /v1/status, for example POST or HEAD. The response carries Allow: GET.Send a GET request.
409ambiguous_tickerOn prices, resolve and metrics, the ticker names more than one security on the requested date; on fundamentals, insider transactions and institutional holdings, it names more than one company. Nothing else in the request resolved.Pick one entry from candidates. On prices, insider transactions and holdings, send its entity_id as the entity_id parameter. On metrics and fundamentals, request a class ticker such as BRK-B, or send the issuer's entity_id, which Resolve returns as issuer_entity_id.
410cursor_expiredThe since cursor or date sent to the change feed is older than the 90 days of records the feed keeps.Pull the data you keep once more, then continue from meta.latest_cursor in the body.
429burst_limit_reachedA Free key, or a keyless request, used its 40 requests in the current clock hour. error reads Burst limit reached (40 requests/hour). Try again shortly.Wait retry_after_seconds, then retry. See Rate limits.
429daily_limit_reachedYour account used its daily requests. error reads Daily limit reached (100 requests/day). Resets at midnight UTC., where the number is your plan's daily limit.Wait retry_after_seconds (until 00:00 UTC), or upgrade.
429rate_limit_exceededKeyless resolve or search requests from one IP address continued after the hourly request limit. error reads Rate limit exceeded.Wait retry_after_seconds, or send a key.
429temporarily_blockedRequests from the sending IP address that carry no valid API key are temporarily refused. error reads Temporarily blocked.Send a valid API key, or contact hello@xfinlink.com.
429hourly_usage_limitYour account passed its rows-per-hour allowance.Wait until the next clock hour (UTC). Request fewer rows with start, end, fields and limit.
429row_limit_exceededYour account passed its rows-per-day allowance.Wait until 00:00 UTC.
429asof_limit_exceededA Free key requested index rosters for more than 50 distinct as_of dates in one UTC day.Wait until 00:00 UTC, or use a paid key, which has no such limit.
500internal_errorThe request failed on the server.Retry after a short wait.
503coverage_unavailableHoldings coverage could not be read. Holdings endpoints only.Retry after a short wait.
504request_timeoutThe request ran past the API's time limit.Narrow the date range or the ticker list, then retry.
504query_timeoutA database query ran past its time limit.Narrow the date range or the ticker list, then retry.

400 Bad request

A 400 means the request itself needs changing. detail names the parameter. Sending the same request again returns the same error.

Too many tickers

Free allows one ticker or one entity_id per request. The response names the Pro allowance.

Terminal
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/AAPL,MSFT"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/fundamentals?entity_id=1,2"

The Free response to two tickers:

response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"Free tier allows 1 ticker per request. Upgrade to Pro for up to 100. https://xfinlink.com/pricing"}

The Free response to two entity_id values:

response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"Free tier allows 1 entity_id per request. Upgrade to Pro for up to 100. https://xfinlink.com/pricing"}

Paid plans return the plain cap. Pro with 101 tickers on prices:

response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"Maximum 100 tickers per request"}

Pro with 11 tickers on resolve, which accepts up to 10:

response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"Maximum 10 tickers per request"}

Invalid dates

Dates are YYYY-MM-DD and must be real calendar dates from 0001-01-01 to 9999-12-31. On fundamentals, start and end run from 0002-01-01 to 9999-11-30. On metrics, start and end run from 0005-01-01 to 9999-11-30, and as_of from 0005-01-01. A malformed date, an impossible calendar date, a date outside these ranges and an end before start are all rejected.

Terminal
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/AAPL?start=2024-1-5"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/fundamentals/AAPL?as_of=2021-02-30"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/fundamentals/AAPL?end=9999-12-31"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/AAPL?start=2024-01-01&end=2023-01-01"

The malformed start:

response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"Invalid start date format (YYYY-MM-DD)"}

The impossible as_of:

response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"Invalid as_of date: '2021-02-30' (expected a real YYYY-MM-DD date)"}

The end outside the fundamentals range:

response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"Invalid end date: '9999-12-31' (expected a date from 0002-01-01 to 9999-11-30 on this endpoint)"}

The end before start:

response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"end (2023-01-01) is before start (2024-01-01)"}

Invalid parameter values

detail names the parameter, and lists the accepted values when the list is short.

Terminal
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/AAPL?interval=2d"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/AAPL?adjust=total"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/fundamentals/AAPL?period=weekly"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/index/dax"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/index/sp500?limit=0"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/search?q=apple&limit=501"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/managers?search=a"

The seven responses, in the order of the commands:

response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"Invalid interval: '2d'. Valid: 1d, 3d, 1w, 1mo, 3mo, 6mo, 1y"}
response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"adjust must be 'split' or 'none'"}
response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"period must be quarterly, annual, or all"}
response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"Unknown index: dax. Available: djia, ndx100, sp500"}
response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"limit must be a positive integer"}
response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"limit max is 500"}
response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"search must be at least 2 characters"}

limit is a whole number of 1 or more, and offset a whole number of 0 or more; a whole number written with a decimal point, such as 50.0, is read as that number. include_amendments, include_derivatives and include_uncoded take true or false, in any case. Holdings quarter must be a calendar quarter end, and manager_id a positive whole number. On metrics, view takes only latest and version only restated. These checks apply to values that are not empty; an empty value, such as limit=, is left to the endpoint.

Terminal
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/AAPL?limit=-1"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/insiders/AAPL?include_amendments=1"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/holdings/AAPL?quarter=2026-03-30"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/metrics/GE?view=as_reported"

The four responses, in the order of the commands:

response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"limit must be a positive integer"}
response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"include_amendments must be true or false"}
response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"Invalid quarter: '2026-03-30' (expected a quarter end: YYYY-03-31, YYYY-06-30, YYYY-09-30 or YYYY-12-31)"}
response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"view must be latest on /v1/metrics: metrics are computed from the latest figures for each period. Pass as_of for the figures known on a date."}

Fields

An unknown name in fields is skipped with a warning. The request fails only when no requested name is valid, and detail lists the accepted names.

Terminal
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/AAPL?fields=price"
response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"No valid fields in request. Unknown field ignored: 'price'. Available fields: open, high, low, close, adj_close, volume, shares_outstanding, return_daily, exchange_code, split_ratio, dividend, market_cap"}

entity_id and cursor

Send a ticker in the path or entity_id as a parameter, not both. entity_id takes positive integers, comma-separated. cursor takes only the next_cursor value from a previous page of the same request: the same path and parameters, with limit free to change. Any other non-empty value returns 400 with a detail that begins Invalid cursor and asks for the request to be repeated without cursor.

Terminal
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/AAPL?entity_id=1"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices?entity_id=abc"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/AAPL?cursor=notacursor"

The three responses, in the order of the commands:

response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"Provide either a ticker in the path or an entity_id parameter, not both."}
response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"Invalid entity_id: 'abc'. Must be a positive integer."}
response.json
{"error":"bad_request","code":"bad_request","status":400,"detail":"Invalid cursor: not a cursor this API issued, or one from an earlier paging scheme. Repeat the request without cursor to start from the first page."}

Page limit

Prices, fundamentals, metrics, insiders, holdings, manager holdings and manager search stop a cursor chain at your plan's page limit. The request for the next page returns 400 with code pagination_limit_reached; error reads Pagination limit reached, and detail and message carry the plan's limit. On Free, the eleventh page of this query is refused:

Terminal
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/AAPL?limit=20&fields=close&cursor=djJ8ZXlKeUlqb2ljSEpwWTJWeklpd2ljU0k2SWs0NGVYTnBMVk5DYjFaT2RrOXJZVTFUYVUwelJIY2lMQ0p3SWpveE1Td2lheUk2SWsxVVFYZE5SRTB5VFhwV09FMVVhelJOVXpCM1QxTXdlVTlJZDNoTlVUMDlJbjB8ZDNjYjliUlV5ZHBWcTVUN1hrY0JMdw=="
response.json
{"error":"Pagination limit reached","code":"pagination_limit_reached","status":400,"detail":"Free tier supports up to 10 pages per query. Upgrade to Pro for 50-page queries: https://xfinlink.com/pricing","message":"Free tier supports up to 10 pages per query. Upgrade to Pro for 50-page queries: https://xfinlink.com/pricing"}

Pro refuses page 51 with:

response.json
{"error":"Pagination limit reached","code":"pagination_limit_reached","status":400,"detail":"Pro tier supports up to 50 pages per query.","message":"Pro tier supports up to 50 pages per query."}

Max refuses page 101 with:

response.json
{"error":"Pagination limit reached","code":"pagination_limit_reached","status":400,"detail":"Max tier supports up to 100 pages per query.","message":"Max tier supports up to 100 pages per query."}

Redistribution refuses page 201 with:

response.json
{"error":"Pagination limit reached","code":"pagination_limit_reached","status":400,"detail":"Redistribution tier supports up to 200 pages per query.","message":"Redistribution tier supports up to 200 pages per query."}

The page-limit 400 does not count against your request quota and carries no quota headers.

401 Unauthorized

401 means the endpoint needs a key and the request carried none the API accepts. detail and message name the cause and the fix.

Terminal
curl "https://api.xfinlink.com/v1/prices/AAPL"
curl -H "X-API-Key: not-a-real-key" "https://api.xfinlink.com/v1/prices/AAPL"
curl -H "Authorization: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/AAPL"
curl -H "api-key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/AAPL"

No key:

response.json
{"error":"unauthorized","code":"unauthorized","status":401,"detail":"No API key was sent. Pass your key in the X-API-Key header or the ?api_key= query parameter. Get one free at https://xfinlink.com/signup","message":"No API key was sent. Pass your key in the X-API-Key header or the ?api_key= query parameter. Get one free at https://xfinlink.com/signup"}

A value that is not xfl_ followed by 32 lowercase hexadecimal characters:

response.json
{"error":"unauthorized","code":"unauthorized","status":401,"detail":"The API key sent is not a complete xfinlink key: a key is xfl_ followed by 32 characters, with no quotes, spaces or Bearer prefix. Copy it with the copy button at https://xfinlink.com/dashboard","message":"The API key sent is not a complete xfinlink key: a key is xfl_ followed by 32 characters, with no quotes, spaces or Bearer prefix. Copy it with the copy button at https://xfinlink.com/dashboard"}

An Authorization header that does not hold Bearer followed by a complete key:

response.json
{"error":"unauthorized","code":"unauthorized","status":401,"detail":"The Authorization header does not read Bearer followed by a complete xfinlink key: a key is xfl_ followed by 32 characters, with no quotes or spaces. Copy it with the copy button at https://xfinlink.com/dashboard","message":"The Authorization header does not read Bearer followed by a complete xfinlink key: a key is xfl_ followed by 32 characters, with no quotes or spaces. Copy it with the copy button at https://xfinlink.com/dashboard"}

A key sent in the api-key header:

response.json
{"error":"unauthorized","code":"unauthorized","status":401,"detail":"The API key was sent in the api-key header, but xfinlink reads it only from the X-API-Key header, an Authorization: Bearer header or the ?api_key= query parameter. Send it there instead.","message":"The API key was sent in the api-key header, but xfinlink reads it only from the X-API-Key header, an Authorization: Bearer header or the ?api_key= query parameter. Send it there instead."}

The same message names the place the key was found: the api-key, apikey, x-api-token or token header, or the apikey, key, token or access_token query parameter.

A key revoked on 2026-09-01:

response.json
{"error":"unauthorized","code":"unauthorized","status":401,"detail":"This API key was revoked on 2026-09-01 and no longer works. Use the current key shown at https://xfinlink.com/dashboard","message":"This API key was revoked on 2026-09-01 and no longer works. Use the current key shown at https://xfinlink.com/dashboard"}

A well-formed key the API does not recognise:

response.json
{"error":"unauthorized","code":"unauthorized","status":401,"detail":"This API key is not recognized. Copy your current key from https://xfinlink.com/dashboard, or get one free at https://xfinlink.com/signup","message":"This API key is not recognized. Copy your current key from https://xfinlink.com/dashboard, or get one free at https://xfinlink.com/signup"}

/v1/resolve and /v1/search answer without a key, but a key sent to them is still checked: a value in X-API-Key or api_key that is incomplete, revoked or unrecognised, or a revoked or unrecognised key sent as Authorization: Bearer, returns the 401 above. With no key, with a key under another header or parameter name, or with any other Authorization value, they return the keyless response; on resolve that is the restricted body, with "restricted": true. See Authentication.

Unknown paths

The key is checked before the path. An unknown path under /v1/ returns 401 without a key and 404 with a valid key.

Terminal
curl "https://api.xfinlink.com/v1/nope"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/nope"

Without a key:

response.json
{"error":"unauthorized","code":"unauthorized","status":401,"detail":"No API key was sent. Pass your key in the X-API-Key header or the ?api_key= query parameter. Get one free at https://xfinlink.com/signup","message":"No API key was sent. Pass your key in the X-API-Key header or the ?api_key= query parameter. Get one free at https://xfinlink.com/signup"}

With a valid key:

response.json
{"error":"not_found","code":"not_found","status":404,"detail":"Unknown endpoint"}

402 Upgrade required

Insider transactions, institutional holdings, manager search and manager holdings need Pro or higher, and a Free key gets 402. The date, limit, include_amendments, include_derivatives, include_uncoded, quarter and cursor checks above, and the check of the institutional holdings manager_id parameter, come first, on every plan: with a Free key, /v1/insiders/AAPL?limit=0 returns 400. The id in the manager holdings path is checked after the 402.

Terminal
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/insiders/AAPL"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/holdings/AAPL"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/managers?search=berkshire"

Insider transactions return:

response.json
{"error":"upgrade_required","code":"upgrade_required","status":402,"detail":"Insider transactions are available on Pro. https://xfinlink.com/pricing"}

Institutional holdings and manager search return:

response.json
{"error":"upgrade_required","code":"upgrade_required","status":402,"detail":"Institutional holdings are available on Pro. https://xfinlink.com/pricing"}

403 Forbidden

A key whose account is disabled returns 403 on every endpoint that reads a key.

response.json
{"error":"Account temporarily disabled. Contact hello@xfinlink.com","code":"account_disabled","status":403,"detail":"Account temporarily disabled. Contact hello@xfinlink.com","message":"Account temporarily disabled. Contact hello@xfinlink.com"}

404 Not found

detail says what was not found.

Terminal
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/ZZZZZZ"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices?entity_id=999999999"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/managers/1/holdings"

The three responses, in the order of the commands. The third needs a paid key; a Free key gets 402 on manager holdings before the id is checked.

response.json
{"error":"not_found","code":"not_found","status":404,"detail":"No matching entities for: ZZZZZZ"}
response.json
{"error":"not_found","code":"not_found","status":404,"detail":"No matching entities for: 999999999"}
response.json
{"error":"not_found","code":"not_found","status":404,"detail":"No manager with id 1"}

No listed security on the date

A ticker or entity_id can name an issuer that had no listed security on the requested date. resolve names the issuer and its entity_id, which issuer-level endpoints accept: /v1/fundamentals?entity_id=315 returns Hess Corp's statements. Prices needs a listed security, so an issuer id with nothing listed in the window returns 404.

Terminal
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/resolve/HES?date=2026-09-01"
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices?entity_id=4&start=2015-01-01&end=2015-06-30"

The resolve response:

response.json
{"error":"not_found","code":"not_found","status":404,"detail":"No listed security on 2026-09-01 for HES: it names HESS CORP (entity_id 315); pass entity_id=315 for issuer-level data."}

The prices response:

response.json
{"error":"not_found","code":"not_found","status":404,"detail":"No listed security on 2015-06-30 for: 4"}

An empty result is not an error. A known ticker with no rows in the requested window returns 200 with an empty data array.

405 Method not allowed

Data endpoints and /v1/status accept GET only. Another method, other than OPTIONS, returns 405, and the response carries an Allow: GET header.

Terminal
curl -X POST -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/AAPL"
response.json
{"error":"method_not_allowed","code":"method_not_allowed","status":405,"detail":"This endpoint accepts GET only. Send a GET request."}

A HEAD request also returns 405, with no body.

409 Ambiguous ticker

409 means the ticker named more than one security, or more than one company, on the date and nothing in the request could be served. candidates lists them, and hint reads pass entity_id=. Prices, resolve and metrics answer 409 when a ticker names more than one security. On a date when both Berkshire Hathaway classes traded, BRK names both:

Terminal
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/BRK?end=2026-09-18"
response.json
{
  "error": "ambiguous_ticker",
  "code": "ambiguous_ticker",
  "status": 409,
  "detail": "BRK names more than one security on 2026-09-18. Request by entity_id to choose one.",
  "candidates": {
    "BRK": [
      {"entity_id": 10006133, "name": "BERKSHIRE HATHAWAY INC DEL", "exchange": "NYSE", "share_class": "A", "last_bar_date": "2026-09-18", "valid_from": null, "valid_to": null},
      {"entity_id": 10025221, "name": "BERKSHIRE HATHAWAY INC DEL", "exchange": "NYSE", "share_class": "B", "last_bar_date": "2026-09-18", "valid_from": null, "valid_to": null}
    ]
  },
  "hint": "pass entity_id="
}

Send one candidate's id to get its series: /v1/prices?entity_id=10025221&end=2026-09-18, or use the class ticker, such as BRK-B. resolve and metrics answer BRK with the same candidates and hint.

On paid plans, which accept several tickers per request, a request in which only some tickers are ambiguous returns 200 with the others served. Prices, fundamentals, metrics, insider transactions and institutional holdings name the ambiguous ones in warnings. Prices, fundamentals, metrics and resolve list them, with their candidates, in meta.tickers_ambiguous. /v1/prices/BRK,AAPL?start=2026-09-18&end=2026-09-18 returns Apple's row and lists BRK there.

Fundamentals, insider transactions and institutional holdings serve the company when a ticker names several share classes of one company. /v1/fundamentals/BRK returns Berkshire Hathaway's statements with the warning BRK named more than one share class on that date; served at the company level. On these three endpoints a ticker is ambiguous only when it names two different companies. SFN named two companies on 2009-06-30, so with a paid key /v1/insiders/SFN?start=2009-06-30&end=2009-06-30 and /v1/holdings/SFN?quarter=2009-06-30 both return the body below, where each candidates entry is a list of entity_id values. Fundamentals lists the candidates as objects, as prices does.

response.json
{"error":"ambiguous_ticker","code":"ambiguous_ticker","status":409,"detail":"SFN names more than one company. Request by entity_id to choose one.","candidates":{"SFN":[10022947,10034604]},"hint":"pass entity_id="}

429 Too many requests

429 means a limit was reached, or requests from the sending address are temporarily refused. The body depends on which one, and code names it: burst_limit_reached, daily_limit_reached, asof_limit_exceeded, hourly_usage_limit, row_limit_exceeded, rate_limit_exceeded or temporarily_blocked. None of them carries a Retry-After header. Request-limit bodies carry retry_after_seconds.

The hourly request limit:

response.json
{"error":"Burst limit reached (40 requests/hour). Try again shortly.","code":"burst_limit_reached","status":429,"detail":"Burst limit reached (40 requests/hour). Try again shortly.","message":"Burst limit reached (40 requests/hour). Try again shortly.","retry_after_seconds":585}

The daily request limit:

response.json
{"error":"Daily limit reached (100 requests/day). Resets at midnight UTC.","code":"daily_limit_reached","status":429,"detail":"Daily limit reached (100 requests/day). Resets at midnight UTC.","message":"Daily limit reached (100 requests/day). Resets at midnight UTC.","retry_after_seconds":43181}

The index as_of allowance:

response.json
{"error":"asof_limit_exceeded","code":"asof_limit_exceeded","status":429,"detail":"Historical index query limit reached. Contact hello@xfinlink.com for extended access."}

The rows-per-hour allowance:

response.json
{"error":"hourly_usage_limit","code":"hourly_usage_limit","status":429,"detail":"Hourly usage limit reached. Try again shortly."}

The rows-per-day allowance:

response.json
{"error":"row_limit_exceeded","code":"row_limit_exceeded","status":429,"detail":"Daily data volume limit reached. Resets at midnight UTC."}

Keyless resolve and search requests that continue from one IP address after the hourly limit can also receive this body. retry_after_seconds is the number of seconds until the next clock hour in UTC.

response.json
{"error":"Rate limit exceeded","code":"rate_limit_exceeded","status":429,"detail":"Rate limit exceeded. Sign up for an API key at https://xfinlink.com/signup","message":"Rate limit exceeded. Sign up for an API key at https://xfinlink.com/signup","retry_after_seconds":...}

Requests that carry no valid API key, from an IP address that is temporarily refused, receive this body, with no retry_after_seconds. Send a valid API key, or contact hello@xfinlink.com.

response.json
{"error":"Temporarily blocked","code":"temporarily_blocked","status":429,"detail":"Requests without a valid API key are temporarily refused from this address. Send a valid API key, or contact hello@xfinlink.com.","message":"Contact hello@xfinlink.com"}

Rate limits covers each limit, the headers and retry code.

5xx Server errors

A 5xx status means the request could not be completed on the server. Retry after a short wait, a few times, with growing gaps.

The 500 body:

response.json
{"error":"internal_error","code":"internal_error","status":500,"detail":"The request failed on the server. Retry after a short wait."}

The 503 body, on holdings endpoints:

response.json
{"error":"coverage_unavailable","code":"coverage_unavailable","status":503,"detail":"Holdings coverage is not available"}

The 504 body when the request runs past the time limit:

response.json
{"error":"request_timeout","code":"request_timeout","status":504,"detail":"Request exceeded time limit"}

The 504 body when a query runs past its time limit:

response.json
{"error":"query_timeout","code":"query_timeout","status":504,"detail":"Query exceeded time limit"}

For the two 504 errors, narrow the date range or split the ticker list before you retry.

Warnings are not errors

Some problems do not stop a request. The API returns 200 and lists them in a top-level warnings array. An unknown field next to a valid one is one example:

Terminal
curl -H "X-API-Key: YOUR_API_KEY" "https://api.xfinlink.com/v1/prices/AAPL?start=2026-09-24&end=2026-09-25&fields=close,price"
response.json
{
  "data": [
    {"entity_id": 10003635, "ticker": "AAPL", "entity_name": "APPLE INC", "gics_sector": "Information Technology", "date": "2026-09-24", "close": 335.92, "price_basis": "trade"},
    {"entity_id": 10003635, "ticker": "AAPL", "entity_name": "APPLE INC", "gics_sector": "Information Technology", "date": "2026-09-25", "close": 341.07, "price_basis": "trade"}
  ],
  "meta": {...},
  "warnings": ["Unknown field ignored: 'price'"]
}

close is in USD per share, as traded. Check warnings whenever you build requests from user input.

The API also adds a warning for:

  • A parameter the endpoint does not read. It is ignored and named, with the parameter meant where the name is a common variant: /v1/prices/AAPL?start_date=2026-09-24 returns Unknown parameter ignored: 'start_date' (did you mean 'start'?).
  • A Free request that reaches before the plan's one-year floor, on prices, fundamentals, metrics and index events: The Free plan covers the last year, from 2025-10-10; rows before that date are not included. Paid plans cover the full history: https://xfinlink.com/pricing. When the whole window ends before the floor, the warning says the window has no rows.
  • A ticker that named several share classes of one company on fundamentals, insider transactions and institutional holdings: BRK named more than one share class on that date; served at the company level.
  • A ticker in a list that named more than one security or company, as described under 409 above.

Errors in the Python client

The client raises xfinlink.XfinlinkError for every error response. The message starts with xfinlink:, includes the API's detail for most 400, 402 and 409 errors, and ends with a suggested fix.

client_errors.py
import xfinlink as xfl

xfl.set_api_key("YOUR_API_KEY")

calls = {
    "unknown ticker": lambda: xfl.prices("ZZZZZZ"),
    "bad parameter": lambda: xfl.prices("AAPL", fields=["price"]),
    "ambiguous ticker": lambda: xfl.prices("BRK", start="2026-09-14", end="2026-09-18"),
}
for name, call in calls.items():
    try:
        call()
    except xfl.XfinlinkError as e:
        print(f"{name}: {e}")
stdout
unknown ticker: xfinlink: Ticker 'ZZZZZZ' not found. Fix: check spelling, or use xfl.search(q='ZZZZZZ') to find the correct symbol.
bad parameter: xfinlink: API error (400): No valid fields in request. Unknown field ignored: 'price'. Available fields: open, high, low, close, adj_close, volume, shares_outstanding, return_daily, exchange_code, split_ratio, dividend, market_cap. Fix: check your parameters and try again.
ambiguous ticker: xfinlink: API error (409): BRK names more than one security on 2026-09-18. Request by entity_id to choose one.. Fix: check your parameters and try again.

With a Free key, xfl.insiders("AAPL") raises:

stdout
xfinlink: API error (402): Insider transactions are available on Pro. https://xfinlink.com/pricing. Fix: check your parameters and try again.

With a Free key past its hourly limit, xfl.prices("AAPL") raises:

stdout
xfinlink: Rate limit reached (Burst limit reached (40 requests/hour). Try again shortly.). Fix: wait 578s, or reduce requests by adding start/end date filters and using max_rows parameter.

The client does not retry 4xx errors, including 429. It retries 502, 503, 504 and network timeouts up to three attempts before it raises. See Python client reference.

Errors in MCP tools

An MCP tool call that fails returns the API's error body as the tool result, marked with isError.

prompt
Get prices for the ticker ZZZZZZ.
tool result
{"tool": "get_prices", "arguments": {"ticker": "ZZZZZZ"}}
{
  "content": [
    {"type": "text", "text": "{\"error\":\"not_found\",\"code\":\"not_found\",\"status\":404,\"detail\":\"No matching entities for: ZZZZZZ\"}"}
  ],
  "isError": true
}

See MCP server.

Errors and your quota

A 404 or 409 from a data endpoint is an answer and uses quota like any other: it counts one request per ticker sent and carries the X-RateLimit-* headers. A refused request does not count and carries no quota headers: every 400, the page-limit 400 included, and the 402, 401, 403, 405, the unknown-endpoint 404 and every 429, the row-allowance, request-limit, index as_of and Temporarily blocked responses included. See Rate limits.

Was this page helpful?