# Identity API — Guía para integradores API de consulta de identidad (DNI / RUC) para Perú. **Base URL (producción):** `https://identity.coremaster.dev/api` > Llama siempre desde tu **backend**. No incrustes la API key en apps web o móviles. --- ## Endpoints | Método | Ruta | Descripción | |--------|------|-------------| | `GET` | `/v1/dni/{dni}` | Consulta persona por DNI (8 dígitos) | | `GET` | `/v1/ruc/{ruc}` | Consulta empresa por RUC | | `POST` | `/v1/identity/name-match` | Verificación de nombre vs DNI (Name Match) | Scopes: - Consultas DNI/RUC: `identity:read` - Name Match: `identity:name_match` ### Headers | Header | ¿Cuándo? | Descripción | |--------|----------|-------------| | `x-api-key` | Siempre | Tu API key | | `Idempotency-Key` | Si tu key lo exige | UUID **único por intento** (no uses el DNI/RUC) | | `x-timestamp` | Si tu key exige firma | Epoch (segundos o ms) | | `x-nonce` | Si tu key exige firma | Valor único por request | | `x-signature` | Si tu key exige firma | HMAC-SHA256 en hex (ver sección Firma) | --- ## Respuestas ### Encontrado (DNI) ```json { "success": true, "data": { "dni": "00890435", "names": "JOE", "paternal_surname": "RIOS", "maternal_surname": "CORAL" } } ``` ### Encontrado (RUC) ```json { "success": true, "data": { "document_number": "20614095131", "business_name": "ACME SAC", "status": "ACTIVO", "address": "AV. EJEMPLO 123", "department": "LIMA", "province": "LIMA", "district": "LIMA" } } ``` ### No encontrado ```json { "success": false, "data": null, "message": "No se encontraron datos para el documento consultado" } ``` Si recibes “no encontrado”, puedes **volver a consultar más tarde**. En muchos casos el dato ya estará disponible en un reintento posterior. Solo las respuestas con `success: true` y `data` distinto de `null` consumen cuota del plan. --- ## Name Match — Verificación de nombre Compara el nombre que declara el usuario con el asociado al DNI. **No** devuelve los datos de la persona; solo score + veredicto. Scope: `identity:name_match` ```http POST /v1/identity/name-match Content-Type: application/json x-api-key: identity_live_... ``` **Body** ```json { "documentNumber": "00890435", "names": "JOE", "paternalSurname": "RIOS", "maternalSurname": "CORAL" } ``` También puedes enviar solo `"fullName": "JOE RIOS CORAL"`. **Response 200 (verificación completada)** ```json { "success": true, "data": { "documentNumber": "00890435", "score": 100, "verdict": "match", "matchedFields": { "names": 100, "paternalSurname": 100, "maternalSurname": 100, "fullName": 100 }, "thresholds": { "match": 90, "partial": 70 } } } ``` | `verdict` | Cuándo | |-----------|--------| | `match` | score ≥ 90 | | `partial` | 70 ≤ score < 90 | | `mismatch` | score < 70 | **Pendiente de validación** Si aún no podemos completar la verificación, responde: ```json { "success": false, "data": null, "message": "La identidad está en proceso de validación. Intenta de nuevo en unos minutos.", "errorCode": "identity.pending_validation" } ``` Reintenta unos minutos después; cuando la validación termine, obtendrás el score. **No encontrado** ```json { "success": false, "data": null, "message": "No se encontraron datos para el documento consultado", "errorCode": "identity.not_found" } ``` Solo las respuestas con `success: true` y `data` distinto de `null` consumen cuota del plan. Pesos cuando envías campos estructurados: nombres 40% + paterno 40% + materno 20%. --- ## Forma de la API key ```text identity_{live|sandbox}_{publicId}_{secret} ``` Para firmar, el **secret** es lo que va después de `identity_{env}_{publicId}_`: ```ts function extractSecret(apiKey: string): string { const parts = apiKey.split('_'); if (parts.length < 4) throw new Error('Invalid API key format'); const keyPrefix = parts.slice(0, 3).join('_'); return apiKey.slice(keyPrefix.length + 1); } ``` --- ## Firma HMAC (si está habilitada) 1. `bodyHash` = SHA-256 de string vacío (GET) en hex `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` 2. Payload (unido con saltos de línea `\n`): ```text {timestamp} {nonce} GET {path} {bodyHash} ``` `path` ejemplo: `/api/v1/dni/00890435` (incluye query si existe). 3. `x-signature` = HMAC-SHA256(secret, payload) en hex. --- ## Errores útiles | HTTP | `errorCode` | Qué hacer | |------|-------------|-----------| | 401 | `signature.*` / `api_key.*` | Revisar key / firma / timestamp | | 403 | `plan.not_active` | Plan inactivo o sin scope | | 400 | `idempotency.*` | Generar un UUID nuevo por intento | | 429 | `rate_limit.*` / `quota.*` | Esperar / subir de plan | --- ## Ejemplos rápidos ### cURL ```bash curl -sS "https://identity.coremaster.dev/api/v1/dni/00890435" \ -H "x-api-key: $IDENTITY_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` ### TypeScript (Node, con firma) ```ts import { createHash, createHmac, randomBytes, randomUUID } from 'node:crypto'; const BASE = 'https://identity.coremaster.dev'; const API_KEY = process.env.IDENTITY_API_KEY!; function extractSecret(apiKey: string) { const parts = apiKey.split('_'); const keyPrefix = parts.slice(0, 3).join('_'); return apiKey.slice(keyPrefix.length + 1); } async function lookupDni(dni: string) { const path = `/api/v1/dni/${encodeURIComponent(dni)}`; const timestamp = Math.floor(Date.now() / 1000).toString(); const nonce = randomBytes(16).toString('hex'); const bodyHash = createHash('sha256').update('').digest('hex'); const payload = [timestamp, nonce, 'GET', path, bodyHash].join('\n'); const signature = createHmac('sha256', extractSecret(API_KEY)) .update(payload) .digest('hex'); const res = await fetch(`${BASE}${path}`, { headers: { 'x-api-key': API_KEY, 'Idempotency-Key': randomUUID(), 'x-timestamp': timestamp, 'x-nonce': nonce, 'x-signature': signature, }, }); return res.json(); } ``` ### .NET (C#) ```csharp using System.Security.Cryptography; using System.Text; using System.Text.Json; public sealed class IdentityClient { private readonly HttpClient _http; private readonly string _apiKey; private readonly string _secret; public IdentityClient(HttpClient http, string apiKey) { _http = http; _apiKey = apiKey; var parts = apiKey.Split('_'); var keyPrefix = $"{parts[0]}_{parts[1]}_{parts[2]}"; _secret = apiKey[(keyPrefix.Length + 1)..]; } public async Task<JsonDocument> LookupDniAsync(string dni, CancellationToken ct = default) { var path = $"/api/v1/dni/{Uri.EscapeDataString(dni)}"; var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); var nonce = Guid.NewGuid().ToString("N"); var bodyHash = Convert.ToHexString(SHA256.HashData(ReadOnlySpan<byte>.Empty)) .ToLowerInvariant(); var payload = string.Join('\n', timestamp, nonce, "GET", path, bodyHash); using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_secret)); var signature = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(payload))) .ToLowerInvariant(); using var req = new HttpRequestMessage(HttpMethod.Get, path); req.Headers.TryAddWithoutValidation("x-api-key", _apiKey); req.Headers.TryAddWithoutValidation("Idempotency-Key", Guid.NewGuid().ToString()); req.Headers.TryAddWithoutValidation("x-timestamp", timestamp); req.Headers.TryAddWithoutValidation("x-nonce", nonce); req.Headers.TryAddWithoutValidation("x-signature", signature); using var res = await _http.SendAsync(req, ct); var json = await res.Content.ReadAsStringAsync(ct); res.EnsureSuccessStatusCode(); return JsonDocument.Parse(json); } } ``` Más ejemplos (Python, PHP, Go, Java, JS): ver el repositorio interno de Coremaster o pide el paquete de SDK a tu contacto. --- ## Checklist de integración 1. Guardar la API key solo en el servidor 2. Enviar `Idempotency-Key` = UUID por intento 3. Si `success === false`, mostrar “no encontrado” y permitir reintento más tarde 4. Manejar HTTP 429 (rate limit / cuota) 5. No exponer detalles de autenticación al frontend