// integrations · public sector · peru
How to validate a RUC against SUNAT from your system
Every Peruvian system that issues invoices ends up needing the same thing: confirming that the RUC someone just dictated exists, belongs to who they say, and is in a condition to receive a document. And almost everyone gets it wrong the same way — querying SUNAT on every keystroke, and treating “active” and “findable” as if they were one field. They are not, and confusing them shows up at tax time.
11 digits
the last one is a check, not another digit
Free
rejecting a mistyped RUC costs you no query
Active ≠ findable
two separate fields deciding separate things
4 routes
to query, and only one scales
Facing this right now? We have solved it in production.
See how we do it →What you can validate without asking anyone
A RUC has eleven digits and the last one is a check digit derived from the first ten. That means a mistyped RUC is caught on your own server, in microseconds, without spending a single query.
The calculation is a modulo 11 over fixed weights: each of the first ten digits is multiplied by its weight —5, 4, 3, 2, 7, 6, 5, 4, 3, 2—, the products are summed, and the check digit is 11 minus the remainder of that sum divided by 11, with two exceptions: a result of 10 becomes 0, and 11 becomes 1. Here it is with SUNAT’s own RUC:
| Digit | 2 | 0 | 1 | 3 | 1 | 3 | 1 | 2 | 9 | 5 |
|---|---|---|---|---|---|---|---|---|---|---|
| Weight | 5 | 4 | 3 | 2 | 7 | 6 | 5 | 4 | 3 | 2 |
| Product | 10 | 0 | 3 | 6 | 7 | 18 | 5 | 8 | 27 | 10 |
The sum is 94; 94 divided by 11 leaves a remainder of 6; and 11 − 6 = 5, which is exactly the last digit of 20131312955. We checked the algorithm against the public RUCs of SUNAT, RENIEC, SUNARP, the PCM and the Ministry of Education, and against those same numbers with one digit altered: all five valid ones pass and all five corrupted ones are rejected.
The actionable part: this check goes first, always. It filters out typing errors, which are most of them, before they consume a query or dirty your database. What it does not do is tell you the RUC actually exists: a number can be arithmetically valid and belong to nobody. For that you have to ask.
“Active” and “findable” are not the same thing
The status says whether the RUC is registered — active, deregistered, temporarily suspended. The condition says whether SUNAT can locate the taxpayer at the address they declared — findable, not findable, not located. Two separate fields, and a RUC can be active and not findable at once.
This is the mistake that costs money. A team validates “that the RUC is active”, approves the supplier and moves on. Months later the problem surfaces: documents issued by a taxpayer in a not findable condition are treated differently, and supporting the VAT credit gets complicated exactly when it can no longer be fixed. The specific rule is SUNAT’s and worth confirming with your accountant — but the design consequence is clear: your system has to store both fields, not one.
And it has to decide what to do with each combination. Blocking the operation is not the same as letting it through with a flag to accounting. That call is a business decision, not a coding one, which is why it belongs in the scope and not in launch week.
The four ways to query (and which one holds up)
There are four routes: SUNAT’s web lookup, the downloadable taxpayer list, the PIDE for State entities, and third-party providers wrapping all of it in an API. Which one applies depends less on your budget than on who you are.
| Route | For whom | What you get | Cost |
|---|---|---|---|
| SUNAT’s web lookup | Anyone | One manual query at a time | Free, not automatable |
| Downloadable taxpayer list | Anyone | The full list, queried inside your own network | Free, but it ages |
| PIDE | State entities | Online lookup from the single entry point | Under agreement |
| Third-party provider | Anyone | A comfortable API over the same data | Paid, and one more middleman |
If you are a public entity, the natural route is the PIDE: the same single entry point you already use for RENIEC. If you are a private company, it usually ends up being a third-party provider — and there it is worth remembering you are putting a middleman between you and the data, with its own uptime, pricing and change policy. The channels and conditions are SUNAT’s and they change: check any guide —this one included— against the official source before committing an architecture.
The trap: querying on every keystroke
The most common design mistake is firing the query while the user types. With eleven digits, that is up to eleven calls for a single RUC — ten of them for incomplete numbers that do not exist.
The correct sequence is the reverse, and it is cheap: wait for eleven digits, validate the check digit on your server, and only then query. Add a cache with an expiry —a RUC does not change its registered name every hour— and query volume drops to a fraction. In a supplier onboarding flow with live search, that single decision is the difference between an integration that scales and one that gets rate-limited.
What to store, and why the number is not enough
Store a snapshot: the RUC, the registered name, the status, the condition and the date you queried. Not a pointer to “whatever SUNAT says today”, because that data changes and the invoice you issued does not.
When someone asks —an audit, your accountant, the client themselves— the question is never “what is that RUC’s status now?” but “what was it the day you issued?”. If your system only stores the number and re-queries, you have no answer. It is the same traceability principle we apply when validating identity against RENIEC: what counts is not the query, it is the trail it leaves.
Common mistakes when validating a RUC
Almost none are coding errors. They are decisions nobody made: which field to look at, when to ask, and what to keep from the answer.
do it like this
- Validate the check digit on your server before querying anything.
- Store status and condition: they are two fields and they decide different things.
- Query only with all eleven digits, and cache the result.
- Keep the snapshot —registered name, status, condition, date— alongside the document.
- Decide during scoping what happens with a not-findable RUC: block or flag.
avoid this
- Firing the query on every keystroke the user types.
- Treating “active” as if it also answered for “findable”.
- Storing only the number and re-querying whenever you need it.
- Assuming a RUC exists just because the check digit adds up.
- Depending on a third-party provider without reading what happens when it goes down.
How we do it
We connect systems to the Peruvian State in production — RENIEC in real time in a national university’s cashier. Validating a RUC is the same discipline applied to companies instead of people.
We work on State integration end to end: the paperwork that gets you access, the integration that holds up under volume, and the trail that lets you answer when someone asks. If your system issues invoices, let’s talk.
In summary
Validating a RUC is two different things and they are worth keeping apart. The first is arithmetic and free: the last of the eleven digits is a check on the previous ten, so a mistyped number fails on your own server without spending a query. The second is asking SUNAT, and there what matters is reading both fields — status says whether the RUC is registered, condition says whether the taxpayer can be located — and storing the answer as a dated snapshot rather than a pointer. With that solved, validation stops being a patch on the form and becomes part of your accounting.
Sources
Does your system need to talk to the State?
RENIEC in production at a national university. You talk to the engineer who would build the integration, not to a salesperson.
