prospex
Sign in

The Zefix REST API: Endpoints, Credentials and Identifiers

11 min read

Zefix combines the records from Switzerland's 26 cantonal commercial registers in one federal index, and most people meet it as a search box at zefix.admin.ch. Its REST API supports company lookups by UID or EHRA ID and name searches. It also exposes relationship data and daily commercial-register publications. Access is free, but the Federal Office of Justice must issue credentials first.

Use REST for targeted lookups and relationship data. For a bulk scan of the register, use the LINDAS dataset described below.

Get API credentials

Every endpoint requires HTTP Basic authentication, named Zefix-Credentials in the OpenAPI specification. Requests without an Authorization header return 401, including company lookups and searches.

You request a username and password from the Federal Office of Justice by writing to [email protected]. Introduce yourself and describe what you are building, including the expected request volume. Use the username and password as HTTP Basic credentials. Configure them once when constructing the HTTP client.

Nothing in the documentation states a rate limit. We self-impose a 0.5-second minimum interval between requests and retry 429 and 5xx responses with exponential backoff. This request rate has not triggered throttling in our production use. It also keeps traffic modest while Zefix publishes no formal limit.

Check the current OpenAPI specification

Zefix publishes an OpenAPI 3.1 specification for the REST API. The raw specification and the Swagger UI are available online. On 12 August 2026, the specification reported API version 2.7.2.3.

Check it for current endpoints and field names. Confirm enum values and validation rules before updating an integration.

There is also an integration environment. Use it while testing authentication and identifier formatting. Check successful responses and error handling there as well.

Base URL and endpoints

The server is https://www.zefix.admin.ch/ZefixPublicREST and the operations sit under /api/v1. Version 2.7.2.3 publishes these ten operations:

PathMethodResponse
/company/uid/{id}GETCompany record in a single-element array
/company/ehraid/{id}GETCompany record as an object
/company/chid/{id}GETCompany record in a single-element array
/company/searchPOSTSearch result records
/legalFormGETLegal forms with their codes
/registryOfCommerceGETThe cantonal registry offices
/registryOfCommerce/byBfsCommunityId/{id}GETThe office covering one commune
/communityGETPolitical communes with BFS numbers
/sogc/{id}GETOne gazette publication
/sogc/bydate/{date}GETEvery gazette publication of one day

Company lookups return 404 when no record exists. The UID and CH-ID routes return a single-element array, while the EHRA-ID route returns an object, so normalize the response before passing it downstream.

A complete UID lookup

The following example accepts a UID in either format, normalizes it, retries on transient errors, and unwraps the single-element array:

import re
import time

import httpx

NORMALISED_UID = re.compile(r"CHE(\d{3})(\d{3})(\d{3})", re.IGNORECASE)
PRINTED_UID = re.compile(r"CHE-\d{3}\.\d{3}\.\d{3}", re.IGNORECASE)


def format_uid(value: str) -> str:
    uid = value.strip().upper()
    if PRINTED_UID.fullmatch(uid):
        return uid
    if match := NORMALISED_UID.fullmatch(uid):
        return f"CHE-{match.group(1)}.{match.group(2)}.{match.group(3)}"
    raise ValueError(f"Invalid Swiss UID: {value!r}")


def company_by_uid(client: httpx.Client, uid: str) -> dict | None:
    api_uid = format_uid(uid)
    for attempt in range(3):
        response = client.get(f"/company/uid/{api_uid}")

        if response.status_code == 404:
            return None
        if response.status_code == 429 or response.status_code >= 500:
            if attempt == 2:
                response.raise_for_status()
            time.sleep(2**attempt)
            continue

        response.raise_for_status()
        data = response.json()
        return data[0] if isinstance(data, list) and data else data

    raise RuntimeError("Unreachable")


with httpx.Client(
    base_url="https://www.zefix.admin.ch/ZefixPublicREST/api/v1",
    auth=httpx.BasicAuth(USERNAME, PASSWORD),
    headers={"Accept": "application/json"},
    timeout=30.0,
) as client:
    company = company_by_uid(client, "CHE444420929")

Format UIDs for lookup

Switzerland's business identification number has a canonical printed form, CHE-444.420.929, and a normalised form with the punctuation stripped, CHE444420929. SHAB publications carry the normalised form. Internal datasets often store the normalised form. The /company/uid/{id} route expects the printed form.

Accept both forms at the boundary and convert the normalised form before building the request path. The format_uid function above validates the input. Malformed identifiers raise an exception before the request reaches the API.

Company response fields

Company identity, status, address, and capital are top-level fields. Relationships such as branches and takeovers are arrays of company references. The fields we read, with the names Zefix uses:

  • name, ehraid, uid, chid: identity.
  • legalSeat and legalSeatId, address, canton: where it sits.
  • status: ACTIVE, CANCELLED, BEING_CANCELLED. Filter or label cancelled entities before passing the record downstream.
  • capitalNominal and its currency. Parse it through Decimal(str(value)) to preserve exact decimal values.
  • deletionDate: set when the entity has been removed from the active register.
  • cantonalExcerptWeb: a URL to the cantonal register's own extract, which is the authoritative document.
  • oldNames: previous names with a sequence number, which supports name-history matching in your own data.
  • headOffices, furtherHeadOffices, branchOffices, hasTakenOver, wasTakenOverBy, auditCompanies: each a list of company references carrying a name, an EHRA ID and usually a UID.

Relationship arrays are a reason to choose REST over the LINDAS bulk dataset. Keep the EHRA ID from each reference so it can be resolved through /company/ehraid/{id}. Treat wasTakenOverBy as a register relationship. Check the associated publication before labelling the event an acquisition.

When to keep the EHRA ID

  • The UID (uid) is the number the whole Swiss administration uses for one legal entity: tax, VAT, customs, social insurance and the commercial register all key on it. It is public, printed on invoices, and the usual cross-dataset join key for a legal entity.
  • The EHRA ID (ehraid) is the register's own row number, a plain integer issued by the federal commercial registry office. It exists to identify one entry in the register.

LINDAS company URIs use the EHRA ID: https://register.ld.admin.ch/zefix/company/{EHRA-ID}. Zefix relationship arrays also carry EHRA IDs, so keep the field when you plan to follow head-office and branch references or takeover relationships.

A branch office has its own EHRA ID while sharing the UID of its head office. Check how branches appear in your data before enforcing one row per UID.

Store both. The REST company response supplies both identifiers, so it can populate the mapping during ingestion.

Searching by name

/company/search is a POST that takes a CompanySearchQuery body. The full parameter set:

FieldTypeNotes
namestringRequired, at least three characters. Matches the beginning of the name, and * is a wildcard.
legalFormIdintegerZefix's internal legal form id, 1 to 999.
legalFormUidstringThe four-character public code from eCH-0097.
registryOfCommerceIdintegerOne cantonal office.
legalSeatIdintegerOne commune, by BFS number.
cantonstringTwo-letter abbreviation.
activeOnlybooleanRestricts results to active entries.
response = client.post(
    "/company/search",
    json={"name": "Migros", "canton": "ZH", "activeOnly": True},
)
response.raise_for_status()
candidates = response.json()

The location filters are mutually exclusive. Send one of registryOfCommerceId, legalSeatId, or canton.

Name matching starts at the beginning of the company name. A search for Migros finds names such as Migros Bank AG. Finding Genossenschaft Migros Zürich requires a leading wildcard. The endpoint has no offset, limit, or cursor, so keep the query specific and filter the returned candidates locally.

Treat name-search results as candidates. Compare the UID and legal seat, then check the status before selecting a record.

Resolve legal forms, communes, and registries

/legalForm, /registryOfCommerce and /community return compact reference lists that change infrequently. They are how the numeric IDs in company and search responses become readable. We cache each list for one day and resolve the IDs locally. A company record gives you legalSeatId and a legal form id. The commune list turns the first into a place name with a canton, and the legal form list turns the second into "Aktiengesellschaft" with its eCH code.

/registryOfCommerce/byBfsCommunityId/{id} answers the question of which cantonal office holds a given commune's entries, which matters when you need the authoritative cantonal extract.

Ingest daily SOGC publications

/sogc/bydate/{date} takes a plain 2026-08-12 and returns every Swiss Official Gazette of Commerce publication of that day, each one paired with the short record of the company it concerns.

A publication carries its sogcId, the publishing cantonal office and its canton, the daily register number and date, the formatted message text, and a list of mutationTypes. mutationTypes lets a pipeline route a capital increase differently from an address change without parsing the formatted legal text. /sogc/{id} fetches one publication back by number when you have stored the id and want to re-read it.

For change tracking, request the route once for each calendar date and store the last completed date. Deduplicate publications by sogcId. Replay missed dates after an outage, and let empty publication days advance the checkpoint.

This produces a log of register events published in SOGC. It is more efficient than polling every company record for changes.

When you need volume, use LINDAS

Use REST when you start with a company or identifier. It also supports date-based retrieval and access to Zefix relationship fields. Use LINDAS when you need to scan a large part of the register. Its SPARQL dataset overlaps with the REST API on core identity and address fields and also includes the organisation purpose. The entity type is admin:ZefixOrganisation and each entity has a stable URI of the form https://register.ld.admin.ch/zefix/company/{EHRA-ID}.

The LINDAS dataset does not include the relationship arrays listed above. A bulk pipeline can collect its base population from LINDAS, then call REST only for companies whose relationships it needs.

Query the data at https://ld.admin.ch/query with SPARQL. Use a URI keyset for pagination: select URIs greater than the last one processed and fetch the fields for that batch. Stop when a page returns empty.

API limits

Company lookups describe the current register state. There is no modifiedSince filter or webhook, so store snapshots if you need field-level history.

For published register events, ingest /sogc/bydate/{date} each day. This avoids polling the full register to find a small number of changes. The gazette also has a separate API, covered in the SHAB API guide.

Zefix covers company identity, status, address, capital, and register relationships. Hiring activity, website changes, trademarks, and press coverage come from other sources. Where Swiss company data actually lives maps those datasets.

Cookie preferences

Necessary cookies always run. The other two are on unless you turn them off.