Appearance
Freelancers
A Saudi merchant may trade on a commercial registration or on a freelance certificate (وثيقة العمل الحر). Verity onboards both.
Two documents, not two shapes of one
They are validated differently, verified by different registries, and say different things about who is contracting. So intake asks which one this merchant holds rather than inferring it from the shape of a number.
| Commercial registration | Freelance certificate | |
|---|---|---|
| Number | 10 digits, starting with 7 | FL-013988291 |
| Issued to | A company or establishment | A person |
| Verified by | Wathq | Lean Tech, against the Ministry's records |
| Also needs | — | The holder's national ID |
Why this is not one field with a looser rule
Before this, a freelancer was either refused at intake — the certificate number does not match the unified-CR format — or entered with the format check switched off, which left no record of which kind of document the number even was.
A system that infers which document it holds will infer wrong eventually, and the consequence is a certificate number sent to the commercial registry. That returns not-found, and not-found reads as "this registration failed" — a statement about the merchant — when the truth is we queried the wrong registry.
Creating one
Choose Freelance certificate (وثيقة العمل الحر) on the merchant form, then give the certificate number and the national ID of the person it was issued to.
Both, always. Lean's verification takes FREELANCER_NUMBER and NATIONAL_ID together — neither alone identifies the document — so a freelancer without a national ID is a registration nothing can ever check. It is refused by the form, by the API, and by a database constraint.
Citizens only
A freelance certificate is issued to Saudi nationals, so the national ID must begin with 1. An Iqama (2…) is not a different format here — it is a person who cannot hold this document, and accepting one would send an application that must fail to the registry.
The certificate number rule is deliberately loose, and the FL- prefix optional: one documented example is not a specification, and a merchant reading the number off their certificate may or may not include it. Lean's verification is the real check on whether the document exists.
What runs at intake
The certificate check runs where the account configured a commercial registration — substituted, not added. Everything else the account requires at intake, such as screening, runs unchanged.
A country with no freelancer provider keeps the registration check rather than requiring one that cannot run there. Only Saudi Arabia has one; there is no Bahraini equivalent integrated.
What the check answers
Two questions, and both must hold:
| Ownership | Does this certificate belong to the national ID we asked about? |
| Status | Is that certificate in force? |
Either alone misleads. A genuine, active certificate belonging to somebody else passes the second and fails the first — and onboarding against another person's document is exactly the outcome this pairing exists to prevent.
Lean publishes a closed list of statuses. Only ACTIVE is a document in force:
ACTIVE · PENDING · CANCELED · REVOKED · EXPIRED · REJECTED
A certificate runs for one year before renewal, so the expiry date underneath is checked as well as the word: an ACTIVE certificate whose expiry has passed does not verify.
An unrecognised answer is not a pass
A status outside that list, or a response that answers neither question, reports unknown — not verified and not refused.
Reporting refused would accuse a merchant on the strength of a field we failed to find. Reporting verified would clear one on the same evidence. This is the same rule every check in Verity follows; see the principle.
A person may hold several certificates. The one asked about is the one judged, matched by number — another of their certificates being active says nothing about this one.
Through the API
registration_type and freelancer_national_id are accepted on create and returned on read:
json
{
"entity": {
"registration_number": "FL-013988291",
"registration_type": "freelance_certificate",
"freelancer_national_id": "1099999002"
}
}Omit registration_type and a commercial registration is assumed, which is what every existing client already sends.
Configuration
Lean Tech reads from the environment. Nothing is committed:
LEANTECH_AUTH_URL the OAuth2 token host
LEANTECH_BASE_URL https://sandbox.sa.leantech.me or https://api2.sa.leantech.me
LEANTECH_CLIENT_ID
LEANTECH_CLIENT_SECRET
LEANTECH_SCOPE optional
LEANTECH_TIMEOUT optional, secondsWhich Lean environment an environment talks to is set by LEANTECH_BASE_URL rather than switched on Rails.env — that is a deployment decision, not one this code should assume.
Not yet exercised against the live API
Everything up to the HTTP call is built and tested, but no verification has been made against Lean. The response mapping is written from their published contract rather than from a response we have seen, and the token endpoint is still to be supplied.
Until one real verification has run, treat a live check as unproven. It will most likely report unknown, which is the correct failure — but not a useful one.
What it costs
Unknown. Lean is not on the rate sheet Verity's cost figures were transcribed from, so its calls are priced as unpriced rather than as nothing — an unknown price is never zero.