EntityFeed API reference
EntityFeed is an MCP server that looks up companies in official registers. This page lists the endpoint, the authentication, the two tools, the fields of a company record, every error code and every limit. All examples use https://entityfeed.com. Replace ef_YOUR_KEY with your own key.
Endpoint and transport
The MCP endpoint is POST /mcp. The transport is MCP Streamable HTTP in stateless mode: there are no sessions, every request stands alone, and the answer is a single JSON body, not an event stream. Any other method on /mcp gets HTTP 405 with the code method_not_allowed and the header Allow: POST.
Send Content-Type: application/json and Accept: application/json, text/event-stream. Without both values in Accept, the server answers with a JSON-RPC error and the message "Not Acceptable". The body must be valid JSON and at most 64 KiB. A body that is not JSON gets HTTP 400 with the JSON-RPC code -32700 (Parse error). A larger body gets HTTP 413 with the code body_too_large. A JSON array is a batch of JSON-RPC messages.
initialize and tools/list need no key. Every tools/call needs a key.
Authentication
Send the key as Authorization: Bearer ef_YOUR_KEY, or as X-API-Key: ef_YOUR_KEY. A missing or wrong key gets HTTP 401 with the code invalid_api_key and the header WWW-Authenticate: Bearer realm="mcp". A call refused with 401 is not counted.
Get a free key with a POST to /register and a JSON body that holds your email address. Registering this way also accepts the terms. The key is shown once and only a hash of it is stored. When email confirmation is switched on, the answer is HTTP 202 with check_your_email, and the key is shown after you open the one-time link in the message. Registration errors are listed under HTTP errors below.
curl -X POST https://entityfeed.com/register -H 'content-type: application/json' -d '{"email":"you@example.com"}'
Tools
Only registers that can answer are offered. Today these are Slovakia (SK) and Czechia (CZ). The tool list on the server is the authority: the country input is an enum of the registers on offer.
search_company: search by name or by company ID. Returns up to 10 matches.get_company: fetch one company by its ID.
Both tools are read only. Every call counts as one query, whatever the result.
search_company
Inputs:
| Input | Type | Rules |
|---|---|---|
query | string | Required. 2 to 200 characters after trimming, with at least one letter or digit. A query of 1 to 8 digits is treated as a company ID and padded with zeros to 8 digits. |
country | string | Required. One of the registers on offer, SK or CZ. |
limit | integer | 1 to 10, default 5. |
include_inactive | boolean | Default false. Slovakia only: also return terminated entities. |
The result is an object with count, retrieved_at, cached, stale and results, a list of company records. The four top level values describe the whole answer. Each record also carries its own retrieved_at, cached and stale.
For a name search of a Slovak legal person, the result comes from the copy of the register (see below), in the order of the copy's matching. An ID search of a Slovak company returns the entity whatever its status.
get_company
Inputs:
| Input | Type | Rules |
|---|---|---|
country | string | Required. SK or CZ. |
id | string | Required. 1 to 20 characters. For SK and CZ it must be 1 to 8 digits; spaces are removed and the ID is padded with zeros to 8 digits. |
The result is one company record. A company that the register does not have is an error with the code not_found.
The company record
All fields are always present. A value the register does not give is null, or an empty list for activities.
| Field | Meaning |
|---|---|
country | SK or CZ. |
id | The company ID in the register, 8 digits. |
id_type | The kind of ID. ICO for Slovakia and Czechia. (company_number is used for the United Kingdom, which is not on offer.) |
name | The current name. |
legal_form | {code, name} or null. Czech records give the code only and name is null. A Slovak record fetched live by name search can also have null. |
status | active, inactive or unknown. |
status_detail | A short reason, such as terminated, or null. |
address | {text, street, city, postal_code, country} of the registered office, or null. Each part can be null. |
founded_on | Date of establishment, YYYY-MM-DD, or null. |
dissolved_on | Date of termination, or null. |
vat_id | The tax number as the register gives it. Czech records can carry the DIČ. Slovak records always have null. This is not a VAT validation. |
activities | A list of {scheme, code, description}. Czech records: up to 10 codes, scheme CZ-NACE 2008, description null. Slovak records: the main activity code with its text (scheme SK NACE) and, on a record fetched with get_company, up to five current free text activities (scheme RPO free text, empty code). |
source | The register, its licence and the attribution to show. See below. |
retrieved_at | When the data is from. See below. |
cached | true when the answer came from the server's cache of an earlier live fetch. |
stale | true when the data may be older than it should be. See below. |
The source object has name, url, license and attribution, always. It has snapshot_date (YYYY-MM-DD) only on a record that came from the Slovak copy: the date of the newest batch of the official export applied to the copy. A live record has no snapshot_date. An instance that runs on recorded test data adds data_mode: "recorded"; a normal instance never does.
retrieved_at is a time in ISO 8601. For a live record that was fetched for this call, it is the time of the fetch. For a cached record, it is the time of the earlier fetch. For a record from the Slovak copy, it is midnight UTC of the snapshot_date.
cached is false for records from the copy, because they are not cached.
stale is true in two cases. A record from the Slovak copy is stale when the newest batch applied is more than 10 days old. A cached record is stale when the register could not be reached with a retryable error and the server answered from an earlier fetch that is at most 7 days old. In the second case cached is also true. If there is no such earlier record, you get the error instead.
Which registers answer from a copy and which live
- Slovakia, legal persons: from a copy of the register that is updated every day, when a copy is present. It is built from the public export of the Ministry of Interior. Natural persons (sole traders) are not in the copy.
- Slovakia, anything the copy does not have, and every Slovak lookup when there is no copy: live from the RPO API.
- Czechia: always live from ARES.
Live answers are cached: Slovak and Czech records for 24 hours, a search for 6 hours, a not found result for 1 hour. A copy answer is never cached. The sources page /data-sources says whether a copy is in use and its date, and GET /health returns rpo_copy with snapshot_date and refreshed_at, or null.
The United Kingdom (Companies House) is implemented, but it is offered only when the server has a Companies House key. It is not offered today. EU VAT number validation is not part of the service.
Errors
There are two kinds of error. A tool error comes back with HTTP 200 and an isError: true result, because the request itself worked. An HTTP error comes back with a 4xx or 5xx status.
### Tool errors
The result has isError: true, and both structuredContent and the text have this shape:
{"error":{"code":"not_found","message":"...","source":null,"retryable":false}}
retryable says whether the same call can succeed later. These are all the codes:
| Code | Meaning | Retry |
|---|---|---|
invalid_input | The ID or the query is not acceptable, for example an ID that is not 1 to 8 digits, or a query without a letter or digit. | No. Fix the input. |
not_found | The register has no company with that ID. A repeated call within an hour is answered from the cache. | No. |
source_unavailable | The register could not be reached, or answered with a server error. | Yes, later. |
source_timeout | The register did not answer in time. The limit is 8 seconds, and 40 seconds for the live Slovak API. | Yes, later. |
source_rate_limited | The register is limiting the server. | Yes, in a few minutes. |
source_not_configured | The register is not set up on the server, for example its key was rejected. | No. It is a matter for the operator. |
source_error | The register rejected the request, or sent an answer that could not be read, or an unexpected error occurred. | Only when retryable is true. |
Input that breaks the tool schema (a query shorter than 2 characters, a limit of 11, a country that is not offered, an unknown tool name) is refused before the tool runs. The result has isError: true and a plain text message that starts with MCP error -32602. It is not the JSON shape above. Do not retry it unchanged. These calls still count as a query.
When a retryable error occurs and the server holds an earlier answer for the same request that is at most 7 days old, it returns that answer with stale: true instead of the error.
### HTTP errors
The body is {"error":{"code":"...","message":"..."}}.
| Status | Code | Meaning | Retry |
|---|---|---|---|
| 400 | invalid_email | Registration: the address is not valid. | No. |
| 400 | terms_not_accepted | Registration with a form: the box was not ticked. | No. |
| 401 | invalid_api_key | The key is missing or wrong. Also on /v1/usage. | No. |
| 403 | registration_closed | Registration is not open. | No. |
| 404 | not_found | The path does not exist. (The same code is used for a tool error, which has HTTP 200.) | No. |
| 405 | method_not_allowed | /mcp accepts POST only. | No. |
| 409 | already_registered | A key exists for that email address. Keys cannot be shown again. | No. |
| 413 | body_too_large | The body is over the size limit (64 KiB for /mcp). | No. |
| 429 | quota_exceeded | The monthly limit of the key is used up. | After resets_at. |
| 429 | too_many_requests | A speed limit was hit. Nothing was counted against the monthly limit. | Yes, after Retry-After seconds. |
| 429 | too_many_registrations | Too many registrations or confirmation emails from your address. | Yes, later. |
| 500 | internal_error | An unexpected error on the server. | Yes, later. |
| 503 | email_unavailable | Registration: the confirmation email could not be sent. | Yes, later. |
quota_exceeded has limit, used, resets_at and plan next to the message, and the headers Retry-After, X-Quota-Limit, X-Quota-Used and X-Quota-Resets. The message says how many queries are used and the date of the reset in UTC.
A tool error that is raised outside a tool call is answered as HTTP 502 with the same shape as a tool error.
Limits
- Per address on
/mcp: 120 requests a minute, every plan, sliding window of 60 seconds.initializeandtools/listcount. Over the limit: HTTP 429,too_many_requests,Retry-After: 60. This is checked first, before the key is read. An IPv6 address is counted by its first 64 bits. - Per key: 30 tool calls a minute, every plan, sliding window. A batch counts one for each tool call and passes whole or not at all, so a batch of more than 30 never passes. Over the limit: HTTP 429,
too_many_requests,Retry-Afterin seconds. A refused call does not use monthly quota. - Per key, per calendar month (UTC): the monthly quota. The free plan has 100 queries. The counter starts at zero on the first day of each month and unused queries do not carry over. Each
tools/callis one query, also when the answer is an error or comes from the cache.initialize,tools/listandGET /v1/usageare not queries. A batch that does not fit into what is left uses nothing and getsquota_exceeded. - Registration: by default 5 registrations an hour per address, over that the answer is
too_many_registrations. When email confirmation is on, there is also a limit on confirmation emails per email address (3 a day) and per address (5 an hour) by default.
Every answer to a tool call carries X-Quota-Limit, X-Quota-Used and X-Quota-Resets. The speed limits are held in memory and start again when the server restarts.
Paid plan
A paid plan with a higher limit is not on sale. The free plan is the only plan you can use today.
Examples
These commands were run against a local instance that serves recorded register responses, not against production. The answers have the shapes shown above. Use the real address and your own key.
Initialize, no key needed:
curl -s -X POST https://entityfeed.com/mcp -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
List the tools, no key needed:
curl -s -X POST https://entityfeed.com/mcp -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
Fetch one company by its ID:
curl -s -X POST https://entityfeed.com/mcp -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' -H 'Authorization: Bearer ef_YOUR_KEY' -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_company","arguments":{"country":"CZ","id":"00177041"}}}'
Search by name:
curl -s -X POST https://entityfeed.com/mcp -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' -H 'Authorization: Bearer ef_YOUR_KEY' -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"search_company","arguments":{"country":"SK","query":"Slovak Telekom","limit":1}}}'
The record is in result.structuredContent, and the same JSON as text in result.content[0].text.
Check the plan and the count of the month:
curl -s https://entityfeed.com/v1/usage -H 'Authorization: Bearer ef_YOUR_KEY'
The answer has this shape: {"plan":"free","period":"2026-11","used":2,"limit":100,"resets_at":"2026-12-01T00:00:00.000Z"}.