Tutorial · Python

How to verify a Spanish company by NIF in Python

Validate the NIF format locally, then resolve the company in BORME and pull officers, capital, and registry coordinates via REST. ~30 lines of Python, no scraping.

Updated 8 October 2026

The problem

You're onboarding Spanish B2B counterparties and need to confirm — before transacting — that a NIF (a) is well-formed, (b) maps to a legally registered company, and (c) returns enough detail to fill an onboarding record. The standard answers are paid scraping vendors (€3K+/year for Creditsafe-grade output) or the BORME PDF feed with a custom parser. There's a faster path.

The full working script (30 lines)

# verify_nif.py — local NIF validator + Prometiam API lookup
import os, re, requests

API = 'https://api.prometiam.com/functions/v1/risk-api'
HEAD = {'Authorization': f'Bearer {os.environ["PROMETIAM_API_KEY"]}'}
NIF_RE = re.compile(r'^[A-HJNPQRSUVW][0-9]{7}[0-9A-J]
#x27;) CONTROL_LETTERS = 'JABCDEFGHI' # mod 10 → letter def valid_nif(nif: str) -> bool: nif = nif.upper().strip() if not NIF_RE.match(nif): return False digits = nif[1:8] last = nif[8] cs = (sum(int(d) for d in digits[1::2]) + sum(sum(divmod(int(d) * 2, 10)) for d in digits[0::2])) % 10 cs = (10 - cs) % 10 if nif[0] in 'ABEH': return last == str(cs) # numeric control if nif[0] in 'KPQS': return last == CONTROL_LETTERS[cs] # letter control return last == str(cs) or last == CONTROL_LETTERS[cs] # either accepted def verify(nif: str) -> dict: if not valid_nif(nif): return {'status': 'invalid_format', 'nif': nif} s = requests.get(f'{API}/companies/search', params={'nif': nif, 'country': 'ES'}, headers=HEAD).json() if not s.get('data'): return {'status': 'not_found', 'nif': nif, 'reason': 'no BORME match'} company = s['data'][0] detail = requests.get(f'{API}/companies/{company["id"]}', headers=HEAD).json()['data'] return {'status': 'verified', 'nif': nif, 'company': detail} if __name__ == '__main__': import sys, json print(json.dumps(verify(sys.argv[1]), indent=2, ensure_ascii=False))

Run with python verify_nif.py A28015865 after exporting PROMETIAM_API_KEY=rk_live_….

What you get back

{
  "status": "verified",
  "nif": "A28015865",
  "company": {
    "id": 812044,
    "company_number": "A28015865",
    "company_name": "TELEFONICA, S.A.",
    "legal_form": "SA",
    "region": "MADRID",
    "city": "MADRID",
    "address": "C/ GRAN VIA 28",
    "postal_code": "28013",
    "activity_code": "6110",
    "activity_code_scheme": "CNAE",
    "capital_amount": 4551929995.36,
    "capital_currency": "EUR",
    "capital_eur": 4551929995.36,
    "status": "active",
    "founding_date": "1924-04-19",
    "contacts": [ /* officers */ ]
  }
}
Customer dashboard screenshot: a company found by its register numberCustomer dashboard screenshot: a company found by its register number
The same lookup in the dashboard: choose the country, type the register number, get the company as one row (1 credit). Shown for a Spanish NIF. Open it in the dashboard

Step 1 — local validation

The NIF format encodes the company's legal form in the first character:

  • A — Sociedad Anónima (SA)
  • B — Sociedad Limitada (SL / SRL)
  • C — Sociedad Colectiva
  • D — Sociedad Comanditaria
  • E — Comunidad de bienes
  • F — Cooperativa
  • G — Asociación / non-profit
  • H — Comunidad de propietarios (typically not commercial)
  • J — Sociedad civil
  • P — Corporación local
  • Q — Public agency
  • R — Religious entity
  • S — State-owned
  • U — Unión temporal de empresas (UTE)
  • V — Other
  • W — Foreign-entity establishment

For a fintech onboarding pipeline, you'll usually accept A, B, F, J, U, and W and reject the rest. The other prefixes are either non-commercial or special public-sector cases that typically need a different KYC path.

The 9th character is a control character that depends on prefix:

  • Some prefixes (A B E H) use a numeric control digit.
  • Others (K P Q S) use a letter from the JABCDEFGHI alphabet at position (10 - mod 10) % 10.
  • The rest accept either form. The validator above implements the lenient rule.

Step 2 — REST lookup

Once the format passes, GET /companies/search?nif=&country=ES returns a single matching row from BORME, including the internal Prometiam id. Then GET /companies/{id} returns the full record:

  • Legal name, legal form, registered address, capital
  • Founding date
  • Registry coordinates: tomo / libro / folio / hoja
  • Current officers and their appointment dates
  • Status (active, dissolved, in liquidation)

For a complete KYB record, also call /sanctions/screen on the legal-entity name and on each current officer name. See /use-cases/kyb-onboarding for the full pattern. The Spain company registry API guide covers the same NIF lookup with the administrators and BORME events, in any language.

Common pitfalls

  • NIF case sensitivity. The first letter is uppercase. Lowercase input must be normalized — the validator above does this.
  • NIE confusion. NIEs (foreign-resident IDs) start with X, Y, or Z. They are personal — never corporate. Reject them at the format step if your flow only accepts companies.
  • Non-NIF inputs. If you accept SIRET, SIREN, or UK company_number on the same form, dispatch to ?country=FR or ?country=GB instead. The validator above rejects anything that isn't a Spanish NIF.
  • Rate limits. Free trial is 500 credits over 14 days, Starter is 1,000 credits/month. For a high-volume onboarding flow, cache the /companies/{id} response for 24 hours.
  • Spanish-language fields. activity_description (the purpose clause) and the address are Spanish-language; activity_code is a CNAE code. If your downstream pipeline needs English translation, do that as a separate step — Prometiam returns the canonical Spanish strings.

FAQ

What is a Spanish NIF?
Tax identifier for Spanish companies. 9 characters: letter prefix (legal form), 7 digits, control character.

Can I validate a NIF without an API call?
Yes. The script above does it locally with the deterministic checksum. The API call afterward confirms the NIF maps to a real registered company.

What if the NIF starts with X, Y, or Z?
Those are personal NIE — reject them if you only accept companies.

Is BORME data real-time?
Daily updates from the official gazette. /coverage returns latest_filing per country.

Does this work for SL companies too, or only SA?
Both — and every other commercial legal form. The validator accepts every prefix BORME publishes.

Related