MCP Server Guide
Connect AI agents directly to the Singula API using the MCP protocol.
What is MCP?
The Model Context Protocol (MCP) is an open standard that allows AI agents to connect with external tools and data. Singula's MCP server exposes all our verification tools as tools that any compatible agent can use.
Connect
The Singula MCP is a remote server (SSE/HTTP), not a binary you install. In Claude Code you connect it with a single command:
claude mcp add --transport sse singula "https://api.singula.mx/mcp/sse?api_key=sk_live_xxx"With project scope it is shared with your team: committed to .mcp.json.
claude mcp add --transport sse singula --scope project "https://api.singula.mx/mcp/sse?api_key=sk_live_xxx"Manual configuration
If you prefer to edit the file by hand, define the server in .mcp.json (project) or ~/.claude.json (user):
{
"mcpServers": {
"singula": {
"type": "sse",
"url": "https://api.singula.mx/mcp/sse?api_key=${SINGULA_API_KEY}"
}
}
}{
"mcpServers": {
"singula": {
"type": "http",
"url": "https://api.singula.mx/mcp",
"headers": { "Authorization": "Bearer sk_live_xxx" }
}
}
}.claude/settings.json does NOT define MCP servers in Claude Code. Definitions live in .mcp.json (project) or ~/.claude.json (user, via claude mcp add). The mcpServers block inside .claude/settings.json is ignored.
The API key can go in the query (?api_key=) or in the Authorization: Bearer header — both are supported. Replace sk_live_xxx with your real API key from the Singula dashboard.
Troubleshooting
The most common errors when connecting the server, and how to resolve them.
Error POSTing to endpoint (HTTP 400)The server ends up in a failed state. This was a server-side bug, already fixed — not your config. If it persists, contact support.
MCP server "singula" has a "url" but no "type"The server block in .mcp.json is missing "type": "sse" (or "http").
The server does not show upIt was defined in .claude/settings.json instead of .mcp.json or ~/.claude.json. Check with claude mcp list.
⏸ Pending approvalRun claude interactively inside the project to approve the server.
Available Tools
The MCP server exposes the following tools that your AI agent can use:
customer_id OR direct data. Tools marked no customer accept customer_id or the value itself (name, phone, email, tax ID, the file’s name and type…). For the data tools (checkBlacklist, getJudicialRecords, getJudicialRecordsMoral, getIntelligence, checkEmail, checkPhone and their Premium variants) they are exclusive: sending both or neither returns a clear error; for the file tools (extractDocument, verifyCsf, verifyPoder, extractIne) omit customer_id to go direct, checkFiel and sendEmailOtp accept at least one, and checkSpei always carries the transfer: its optional customer_id only says which file it lands in. No new tools were added for direct queries: same tools, one more way to call them. A direct call touches no customer and charges exactly the same, and sandbox returns the same mocks. A customer_id from another organization gets exactly the answer of one that does not exist; getRisk and getRiskSignals also read only the customers of your key’s environment.
createCustomer
Create physical person with personal data
createMoralCustomer
Create moral person (company)
getCustomer
Complete customer detail. Every request in its history carries bundled_with: the product that ran it internally, included and not charged, or null. Its gender is null when it was never captured
getCurp
Get a customer’s official CURP by searching RENAPO with their details; if RENAPO has no such person it answers found: false, never an invented one (without a customer: calculateCurp, with name holding only the given name or names and the surnames in last_name / mothers_last_name: with the full name a different CURP may come out). It needs their sex: a customer without one (null, never captured) answers 409
validateCurp
Validate a customer CURP against RENAPO (without a customer: validateCurpDirect). match compares names, surnames and CURP ignoring accents, case, punctuation and spaces; its gender is informational: RENAPO’s sex is the one that holds
getRfc
Compute a customer RFC (without a customer: calculateRfc, with the same name rule: only the given name or names, the surnames apart)
validateRfc
Validate a customer RFC against SAT (without a customer: validateRfcDirect)
extractIne
no customerINE/IFE reading + Lista Nominal Vigente, in two steps: it reserves and returns each photo’s signed S3 POST with a ready-to-run curl. The photos never go through the API. With customer_id, the RENAPO cross-check is included at no extra charge
completeIneExtraction
Step 2 of extractIne: confirms both photos are in S3, starts the reading and the Lista Nominal (that is where it charges; if it fails after the card was read — the Lista Nominal under maintenance included —, its base charge) and waits for the result
checkBlacklist
no customerSearch sanctions lists (OFAC, EU, UN, UK). Runs without KYB, on the trial credit too. For a person, name carries only the given name or names and the surnames go in last_name / mothers_last_name; since 1.115.0 a full name in name is tolerated: a surname it already ends with is not searched twice. For companies, name is the trade name and legal_name, optional, the registered name: both are screened in a single query. With customer_id, the sex searched is RENAPO’s when their CURP was validated, else the captured one, and with neither none is sent
getJudicialRecords
no customerJudicial records for physical person. Needs the KYB: without it the answer is a 403, and judicial records run inside a KYC flow, after an approved identity check that names the customer
getJudicialRecordsMoral
no customerJudicial records for moral person. Needs the KYB. Optional legal_name: registered name and trade name are searched in a single query
getIntelligence
no customerDigital footprint (getIntelligencePremium is Digital footprint Premium: it adds the profile synthesis): the public footprint of a person or a company on the web, news, images and social networks. It does not include judicial records, sanctions lists or SAT checks: those are separate tools. With customer_id the customer type decides (a moral person is a company). Without a customer, a person carries name (only the given name or names), last_name (required) and mothers_last_name —since 1.115.0 a full name in name is tolerated: a surname it already ends with is not searched twice—; a company, subject: "company" + name (trade name) and, when you have them, legal_name, rfc (12 characters) and website. Same charge for both. The report carries subject and, on every web result and news item, about_subject (yes, no or unknown), sentiment toward the subject (negative when it reports an adverse fact of theirs; otherwise positive, negative or neutral when the tone is clear and it is not about someone else; otherwise neutral) and adverse ({ category, role }, or null when it reports no adverse fact; adverse only with role accused, detained, convicted or sued). LinkedIn and social profiles carry about_subject too. sentiment_summary sums up the tone of what is about the subject: { positive, neutral, negative, overall, unconfirmed_adverse }, with overall none when nothing is about the subject, negative when any of it is adverse, and otherwise the tone that repeats most (mixed on a tie, neutral when there is neither); unconfirmed_adverse counts apart the adverse items that carry their name but could not be confirmed as about them. excluded counts what was left out: namesakes (another person with the same name), brands_or_companies (a brand or a company; for a company subject, also another company with the same name) and other (anything else). In a flow it runs in a KYC, on the person, and in a KYB, on the company. If it fails, the intelligence.completed webhook (since 1.117.0, the Digital footprint event: documents and e.firma have their own) arrives signed, with status: error
createVerification
Create the KYC link (INE, passport or resident card + selfie + liveness). Runs without KYB: the person has to be there. Optional: redirect_url, external_id, metadata, document_types (ine, passport, resident_card; all three by default), language, expires_in_hours. Included at no extra charge: with an INE, a passport or a resident card and a customer that has a CURP, the document’s check against RENAPO (it reuses the CURP validation or runs one internally) and, for an INE, the Lista Nominal, which lands after the verdict. A passport is read through its MRZ — check digits, the printed data and the CURP against the MRZ, its expiry — and runs the same liveness check as the INE: an expired one is never verified, and a number that fails its check digit leaves the session rejected. The INM resident card (since 1.130.0; temporary or permanent) is read through the MRZ on its back, under the same rule: critical check digits, the printed data and the CURP against the MRZ and its printed expiry; a permanent one does not expire. The session verifies the document submit names ({ document_type }, since 1.130.0) or, without it, the one of the last document photo uploaded; a photo of a document the session does not offer answers 400 document_type_not_offered. The public capture routes have per-minute ceilings (presign and confirm 600 per session and 3000 per IP, submit 60 and 600, the pre-check 300 and 3000): past one, 429 with Retry-After. A session that fails after a paid call is charged its base; before one, nothing
getVerificationStatus
Session state and verdict: session_status, decision, score, verdict, the full result and network, where the person connected from
getVerificationResult
The same plus the signed photos; the liveness frames only when you ask for them
getVerificationFiles
Signed URLs for the photos of a customer’s latest verification
searchCustomers
Search customers by name or ID
listCustomers
List all customers paginated
extractDocument
no customerReserves the reading of any document and returns its signed S3 POST with a ready-to-run curl: the file never goes through the API. With completeDocumentUpload it returns the type, the data, the checklist, authenticity, the file’s risk summary and the decision it supports under your organization’s rules (accept, review, reject or pending, with its reason). Every signal and every requirement carries its values in params. It also recognizes the passport and the resident card (pasaporte and tarjeta_residente, category id_document) and returns their MRZ cross-check in the mrz block — status consistent, inconsistent or unverifiable, each printed field against the MRZ and the validity: the MRZ date on a passport when its check digit passes, the printed expiry on the card, indefinite on a permanent one —; an MRZ that contradicts the printed page adds the mrz_printed_mismatch signal (medium: review, never forged on its own). Such an ID fills no dossier component: identity comes from the identity verification. In sandbox, mock_kind passport or residentCard
verifyCsf
no customerTax certificate cross-checked with the SAT registry. It reserves and returns the signed S3 POST with its curl; it ends with completeDocumentUpload
verifyPoder
no customerPower of attorney: attorney, faculties and validity. It reserves and returns the signed S3 POST with its curl; it ends with completeDocumentUpload
completeDocumentUpload
Step 2 of extractDocument, verifyCsf and verifyPoder: confirms the file is in S3 — this is where an encrypted PDF’s password and the declared values go —, starts the reading and waits for the result, SAT validation included. It is charged when it ends — extractDocument per page, a multi-page TIFF too; verifyCsf and verifyPoder, a flat charge — and only if the file got read: one locked without its password, empty, too large or not an image or a PDF is not charged; a read one is charged even if it comes out unreadable, and its base if the processing fails afterwards
checkFiel
no customere.firma and CSD certificates of a tax ID, with their validity. Its webhook is fiel.checked (new in 1.117.0)
checkSpei
no customerSPEI tracking: confirms with Banxico whether a transfer exists and its status; with a settled payment, who sent it and who received it, and whether there is a receipt (CEP). The transfer data always goes in; customer_id only says which file it lands in. Its webhook is spei.checked (new in 1.123.0)
validateClabe
CLABE validator, no charge: check digit, bank and plaza of a CLABE; it also recognizes a debit card and a mobile phone. Direct only
sendEmailOtp
no customerSend a 6-digit code and profile the domain
verifyEmailOtp
Verify the 6-digit code that was received. send_failed: that code never went out and its send was not charged; send another one
kybExpediente
Cross-check what a company’s file already holds: 30 controls over eleven components, each with holds_approval (since 1.114.0: true when THAT control is what keeps the dossier from pass). The INE is checked against RENAPO with the CURP it prints: a KYB run’s close does it included, at no charge, and this tool on its own only reuses a validation of that CURP that already exists. Since 1.130.0, with no INE read and the ine component required, the representative’s identity session (INE, passport or resident card, with liveness) is their ID when it is VERIFIED and belongs to the person the power of attorney or the deed names (otherwise ine is missing: review); its image-quality advisory never fails, and ine_source says where it came from (ine_extraction or identity_session). Person names are compared with kyc.name_match_threshold (80 % by default) and with both surnames, the signer has to be one of the power of attorney’s attorneys (and a signer’s declined KYC is false), and a deed with no RFC only stops holding when its name is identical to the tax certificate’s. With flow_id it answers that flow coverage instead — what would be reused and what is missing, with its how — crossing nothing, recording nothing and charging nothing, on the PUBLISHED version window; without it, it adds flows[] so you can pick which one to run
kycExpediente
Cross-check what a person’s file already holds: 19 controls, 14 over nine components, 2 on the identity session’s document (a passport since 1.128.0, a resident card since 1.130.0: identity_doc_vigente, critical — a permanent card does not expire —, and identity_doc_dob_vs_renapo, its date of birth against RENAPO; both null for an INE) and 3 network ones (where the person connected from, read from their identity session), each with holds_approval (since 1.114.0: true when THAT control is what keeps the dossier from pass). With no separate INE, the three INE controls read the identity session: its document against RENAPO and its Lista Nominal. Since 1.130.0, when that session read a passport issued outside Mexico that prints no CURP, of a holder who is not a Mexican national, the curp component comes back in excused (a foreigner has no CURP) and holds nothing; with a resident card, ip_country_vs_document accepts an IP from Mexico or from the country of its nationality. Inside a run, form_vs_renapo compares RENAPO with what the person captured, never with what a step copied onto the record. With flow_id it answers that flow coverage instead — what would be reused and what is missing, with its how — crossing nothing, recording nothing and charging nothing, on the PUBLISHED version window; without it, it adds flows[] so you can pick which one to run
getVerificationPolicy
How strict your verification is: thresholds (payslip, invoice and payment receipt start with no age limit; the bank statement, under the proof-of-address one), rules, whether you demand a verifiable digital origin, what a standalone document’s decision does with each condition (decision_rules, which start as the usual decision), the KYB dossier’s 30 controls, the KYC dossier’s 19 — two on the passport or the resident card, its expiry (critical) and its date of birth against RENAPO (not critical), and three network ones, on and not critical: they send to review, never decline or approve on their own — and direct-query retention
updateVerificationPolicy
Move the thresholds —including an age limit of their own for the bank statement, the payslip, the invoice or the payment receipt—, demand a digital origin, set what a standalone document’s decision does with each condition (forged, suspicious, failed requirement, no metadata, scanned or unconfirmed by the SAT; a write without decision_rules resets them to the default), switch a control off, lock either dossier’s bar or expire direct queries. Its kyc.name_match_threshold, the person-name threshold, since 1.114.0 also judges the KYB’s people: the attorney against the INE and against the deed, and the signer against the power of attorney
listFlows
List your onboarding flows; optional kind filters kyc or kyb
getFlow
One flow in detail: steps with their rules and their branches (when), published version, editor canvas, estimate and problems — that is where unmet_need shows up when a step reads something neither the form asks for nor an earlier REQUIRED, unbranched step produces, invalid_form_field or invalid_rule when the data step questionnaire or its rules are badly built, invalid_condition when a branch is badly built — since 1.114.0 also a value the field never holds (verification.status eq approved) or an operator its type does not take: branches that never opened —, form_cannot_create_customer when the data step does not ask, as required, for the fields the customer is created from (KYC: name and last_name; KYB: legal_name or name), and repeated_tool when it repeats a tool: only the document step, the representatives step and the same tool on branches that exclude each other (e.g. «if foreign» / «if Mexican») repeat
getFlowCatalog
The catalog a flow is built from: available steps, your charges — a step charges what its tool charges on its own, with no flow discounts —, what your plan unlocks — and in after_identity, what an account with no KYB runs only after an identity step —, conditions — the sources, the operators and the fields a branch may read —, each step provides (which value it leaves for the ones that follow) and form_fields: how the data step questionnaire is built, with its ten types, the known fields and the limits
listFlowTemplates
The ready-made flow templates; optional kind filters kyc or kyb. Each one carries its key, its steps and the estimated charge at your prices
createFlowFromTemplate
Create a draft with a template’s steps (key); optional name. It is born a draft: publishing is still yours to do
createFlowSession
Open a verification of a published flow and return its link and token. channel: "hosted" (our page, the default) or "api" (your front drives it; demands customer_id). With reuse_evidence: true — which also demands customer_id — the steps whose fresh check already exists are born resolved and charged 0, and the response carries reused[] and missing[] with their how. A file of «Upload your documents» that no step uses is charged once, at its standalone charge, only if it was already read
getFlowSession
State and verdict of a verification; with wait it blocks until the decision. Every step says whether it was born reused, so one charged 0 is never confused with an attached one. It also carries attempt and attempts[] per step, resubmission_count and resubmissions[] (the resubmission rounds), form_data, the questionnaire’s own answers, link_id, the shareable link that opened it, network: where the person connected from (up to 10 IPs with country, ASN and Tor, hosting or Private Relay flags; null = no IPs; on the API channel only the person’s browser counts, never your server), and unused_uploads[]: the files of «Upload your documents» no step used, with why and what each one added (0 if it was never read)
nextFlowStep
API channel: which step is next and how to resolve it (how: api | sdk | wait). done: true = time to close. It carries attempt, and resubmission when a reviewer reopened the step; the data step with questions of its own comes out with interaction: form and input_contract, and an upload step with upload (init and complete): the file goes straight to S3. Since 1.114.0, when that attempt’s identity capture was already sent (e.g. the page reloaded), identity_start answers status: "submitted" with no token: do not open another one, the run waits for the verdict of the one sent
submitFlowForm
Answer the data step (the questionnaire): { <name>: value } per field. It validates like the page; an error is invalid_form with problems[] per field. Answers of the form’s own go to form_data, never to the customer or the webhook. No charge
attachFlowStep
API channel: attach to a step the request_id of a query you already ran on that customer. On a reopened step, the query has to be newer than the resubmission
closeFlowSession
API channel: close the run, build the dossier and return the verdict and the charges. In a production KYC whose identity read an INE it may answer with the run still open: it waits for that Lista Nominal (up to 10 min) and the verdict arrives through flow.completed or getFlowSession. Since 1.114.0, on a KYB it checks the representative’s INE against RENAPO with the CURP it prints (included, at no charge) and waits a few seconds for that lookup: when it is still running, it also answers with the run still open. Since 1.130.0, when the representative’s ID is their identity session, it checks it the same way and, if it read an INE, also waits for its Lista Nominal. On the hosted page, a run whose steps have all finished closes on the page’s next poll, without waiting for the minute rescue
decideFlowSession
Close a run in review by hand: approved or declined, with a note. Deciding is not requesting a resubmission
resubmitFlowSession
Ask the person to repeat steps of a run in review or declined, on the same link: steps, reason, an internal note and optional expires_in_hours. It turns resubmitted and the dossier judges again at the close. Repeating is not charged (if what paid failed after a paid call — a document that was read and failed, an INE, an identity —, the next one pays only the difference up to its own charge: 0 on a fixed charge); up to 3 rounds
submitFlowRepresentatives
KYB: deliver the company’s people (name, email, role) and open one child KYC run for each. Since 1.114.0 the dossier that closes the run reads only these children, and the signer has to be one of the power of attorney’s attorneys
listFlowSessions
List runs; filter by flow_id, status (resubmitted included), channel, parent (the children of a KYB) and link_id (the ones a shareable link opened)
createFlowLink
Create a shareable link (flk_) and its QR on a published flow that starts with the data step and demands no customer: every use opens a new verification and a new customer. label, plus optional max_sessions and expires_at, but a production link always has both: without them, 100 uses and 30 days (null is 400 link_requires_cap / link_requires_expiry); a sandbox key creates sandbox links, where both are optional. The link charges nothing: each verification is charged like any other
listFlowLinks
A flow’s links with their status (active, paused, expired or exhausted), their uses, their ceiling, their expiry and their url, the QR’s
updateFlowLink
Pause, resume or edit a link without deleting it (link_id is its id, not its token): active false switches a printed QR off and null in max_sessions or expires_at removes the ceiling or the expiry only on a sandbox link (on a production one it is 400 link_requires_cap / link_requires_expiry)
listCustomerRelations
A company’s people with their role and their verified identity, or the companies a person represents
createCustomerRelation
Relate a person to a company by hand, with their role (legal representative, attorney, partner or controlling owner)
checkEmail
no customerVerify email address
checkPhone
no customerVerify phone number; since 1.113.0 an invalid one is charged the same when its lookup had a cost
A failure is charged only if it already cost. On documents, INE, identity, e.firma, SPEI tracking and investigation, a check that fails after its paid call is charged its base, even when the failure is ours (an INE already read whose Lista Nominal was under maintenance, for instance); one that fails before it, or where no source replied, is not charged. On CURP, RFC, phone, lists, court records and email a failure is not charged. What is delivered is charged as always. Repeating a capture on a flow’s page is free, and an email send that failed (send_failed) is never charged.
Files: straight to S3
No tool sends the file through the API. Reading a document or an INE takes two calls: the first one (extractDocument, verifyCsf, verifyPoder or extractIne) reserves the query with the file’s name and type and returns the signed S3 POST with a ready-to-run curl; that curl runs where the file is — your machine or your server —, with its real path, and the second one (completeDocumentUpload or completeIneExtraction) confirms, starts the reading and waits for the result. Nothing is charged until you confirm, and the POST expires in 10 minutes.
{
"request_id": "6622f8a1c1d2e3f4a5b6c7d8",
"upload": {
"url": "https://singula-documents.s3.amazonaws.com/",
"fields": { "key": "documents/incoming/6622f8a1c1d2e3f4a5b6c7d8.pdf", "Content-Type": "application/pdf", "Policy": "…", "X-Amz-Signature": "…" }
},
"curl": "curl -sS -o /dev/null -w '%{http_code}\\n' -X POST 'https://singula-documents.s3.amazonaws.com/' …",
"next": {
"tool": "completeDocumentUpload",
"arguments": { "request_id": "6622f8a1c1d2e3f4a5b6c7d8" }
}
}curl -sS -o /dev/null -w '%{http_code}\n' -X POST 'https://singula-documents.s3.amazonaws.com/' \
--form-string 'key=documents/incoming/6622f8a1c1d2e3f4a5b6c7d8.pdf' \
--form-string 'Content-Type=application/pdf' \
--form-string 'bucket=singula-documents' \
--form-string 'X-Amz-Algorithm=AWS4-HMAC-SHA256' \
--form-string 'X-Amz-Credential=…' \
--form-string 'X-Amz-Date=…' \
--form-string 'Policy=…' \
--form-string 'X-Amz-Signature=…' \
-F 'file=@/ruta/a/recibo-cfe.pdf'
# → 204S3 answers 204. Then the agent calls completeDocumentUpload with the request_id (that is where an encrypted PDF’s password goes). If the file did not land, the answer is 409 upload_not_received: upload it with the same POST while it has not expired, or reserve again. The INE takes two POSTs, one per side (uploads.ine_front and uploads.ine_back, with curl.ine_front and curl.ine_back), and is confirmed once with completeIneExtraction.
Usage Examples
Once configured, your AI agent can use the tools directly:
Full identity verification
"Create a customer with this data and validate their CURP against RENAPO"
Background check
"Search the judicial records and blacklist for this customer"
Automated due diligence
"Run a full OSINT investigation on customer cus_abc123"
Onboarding a company
Onboard Constructora Demo: verify its tax certificate, read the deed and the power of attorney, check the e.firma and build the dossier.
A one-off query, no customer
Is +52 55 1234 5678 a real phone and whose is it? And check whether Juan Pérez García is on any sanctions list. Do not save him as a customer yet.
Compatible Clients
Singula's MCP server works with any client that supports the protocol:
Claude Desktop
Official Anthropic app
Cursor
AI-powered IDE
Claude Code
Claude CLI for development
Windsurf
IDE with AI agents
Cline
VS Code extension with MCP