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.
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 */ ]
}
}

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 ColectivaD— Sociedad ComanditariaE— Comunidad de bienesF— CooperativaG— Asociación / non-profitH— Comunidad de propietarios (typically not commercial)J— Sociedad civilP— Corporación localQ— Public agencyR— Religious entityS— State-ownedU— Unión temporal de empresas (UTE)V— OtherW— 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, orZ. 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_numberon the same form, dispatch to?country=FRor?country=GBinstead. 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_codeis 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
- What is BORME? — the Spanish company registry explained
- SIREN vs SIRET vs NIF — picking the right identifier across countries
- Screen sanctions in JavaScript — the post-NIF compliance leg
- KYB onboarding pattern — the full production flow
- Iberinform alternative