VerifyMX Docs

API reference

Every endpoint, every request/response schema, with curl examples.

Base URL: https://api.verifymx.xyz
Version: v1
Auth: All endpoints except /v1/auth/* and /v1/otp/* require Authorization: Bearer <token>.
Content-Type: application/json unless noted as multipart.

The machine-readable OpenAPI spec is at GET /v1/openapi.json.


Error Format

All errors use a consistent envelope:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Human-readable description",
    "details": [...]
  }
}

Common error codes:

CodeHTTPMeaning
VALIDATION_ERROR422Request body or query string failed schema validation
UNAUTHORIZED401Missing, expired, or invalid token/API key
FORBIDDEN403Authenticated but insufficient role
NOT_FOUND404Resource does not exist or belongs to another org
CONFLICT409Duplicate resource (e.g. org slug already taken)
RATE_LIMITED429Exceeded 10 req/s per org
INTERNAL_ERROR500Unexpected server error

Authentication

POST /v1/auth/register

Creates a new organization, admin user, and first API key. The raw API key is shown once in the response — store it immediately.

Auth required: No

Request body:

FieldTypeRequiredDescription
orgNamestringYes2–100 chars. Determines the org slug.
emailstringYesAdmin user email address
passwordstringYes12–128 chars
curl -X POST https://api.verifymx.xyz/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "orgName": "Acme Corp",
    "email": "admin@acme.mx",
    "password": "supersecretpass123"
  }'

Response 201:

{
  "org": {
    "id": "org_01HXYZ...",
    "name": "Acme Corp",
    "slug": "acme-corp"
  },
  "user": {
    "id": "usr_01HXYZ...",
    "email": "admin@acme.mx",
    "role": "OWNER"
  },
  "apiKey": {
    "id": "key_01HXYZ...",
    "name": "Default key",
    "key": "vmx_a1b2c3d4e5f6g7h8i9j0...",
    "prefix": "vmx_a1b2c3d4",
    "createdAt": "2026-04-08T12:00:00.000Z"
  }
}

POST /v1/auth/token

OAuth 2.0 client_credentials token exchange. Returns a JWT valid for 1 hour.

Auth required: No

Request body:

FieldTypeRequiredDescription
grant_typestringYesMust be "client_credentials"
client_idstringYesYour org.id
client_secretstringYesYour raw API key (vmx_...)
curl -X POST https://api.verifymx.xyz/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "org_01HXYZ...",
    "client_secret": "vmx_a1b2c3d4e5f6..."
  }'

Response 200:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Verifications

POST /v1/verifications

Creates a new verification session.

Auth required: Yes

Request body:

FieldTypeRequiredDescription
workflowIdstringYesID of an active workflow
metadataobjectNoArbitrary key-value pairs stored with the verification
callbackUrlstringNoURI to POST results to on completion (in addition to webhooks)
externalIdstringNoYour internal user/session ID (max 255 chars)
curl -X POST https://api.verifymx.xyz/v1/verifications \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "workflowId": "wf_01HXYZ...",
    "externalId": "user-42",
    "metadata": { "planTier": "premium" }
  }'

Response 201:

{
  "verification": {
    "id": "ver_01HXYZ...",
    "status": "PENDING",
    "workflowId": "wf_01HXYZ...",
    "externalId": "user-42",
    "metadata": { "planTier": "premium" },
    "ipAddress": "203.0.113.10",
    "createdAt": "2026-04-08T12:00:00.000Z",
    "updatedAt": "2026-04-08T12:00:00.000Z"
  }
}

GET /v1/verifications

List verifications for your organization with optional filtering.

Auth required: Yes

Query parameters:

ParamTypeDefaultDescription
statusstringFilter by status: PENDING, IN_PROGRESS, APPROVED, REJECTED, EXPIRED, MANUAL_REVIEW
limitinteger20Results per page (1–100)
offsetinteger0Pagination offset
searchstringFull-text search against external ID, metadata (max 255 chars)
curl "https://api.verifymx.xyz/v1/verifications?status=APPROVED&limit=10&offset=0" \
  -H "Authorization: Bearer $TOKEN"

Response 200:

{
  "verifications": [
    {
      "id": "ver_01HXYZ...",
      "status": "APPROVED",
      "externalId": "user-42",
      "createdAt": "2026-04-08T12:00:00.000Z"
    }
  ],
  "pagination": {
    "total": 142,
    "limit": 10,
    "offset": 0,
    "hasMore": true
  }
}

GET /v1/verifications/:id

Retrieve a single verification with all step results and uploaded documents.

Auth required: Yes

curl "https://api.verifymx.xyz/v1/verifications/ver_01HXYZ..." \
  -H "Authorization: Bearer $TOKEN"

Response 200:

{
  "verification": {
    "id": "ver_01HXYZ...",
    "status": "APPROVED",
    "workflowId": "wf_01HXYZ...",
    "steps": [
      {
        "id": "vs_01...",
        "type": "DOCUMENT_OCR",
        "status": "PASSED",
        "result": {
          "name": "JUAN PÉREZ GARCÍA",
          "curp": "PEGJ800101HMCRRN09",
          "dateOfBirth": "1980-01-01"
        }
      }
    ],
    "createdAt": "2026-04-08T12:00:00.000Z",
    "updatedAt": "2026-04-08T12:01:00.000Z"
  },
  "documents": [
    {
      "id": "doc_01...",
      "type": "front",
      "mimeType": "image/jpeg",
      "fileSize": 512000,
      "createdAt": "2026-04-08T12:00:30.000Z"
    }
  ]
}

PATCH /v1/verifications/:id/status

Manually set verification status. Requires REVIEWER role or higher.

Auth required: Yes (REVIEWER+)

Request body:

FieldTypeRequiredDescription
statusstringYesAPPROVED, REJECTED, or MANUAL_REVIEW
notesstringNoReviewer notes (max 2000 chars)
curl -X PATCH "https://api.verifymx.xyz/v1/verifications/ver_01HXYZ.../status" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": "APPROVED", "notes": "Documents verified in person"}'

Response 200:

{
  "verification": {
    "id": "ver_01HXYZ...",
    "status": "APPROVED",
    "updatedAt": "2026-04-08T12:05:00.000Z"
  }
}

POST /v1/verifications/:id/inputs

Upload document images and selfies for processing. Send as multipart/form-data.

Auth required: Yes
Content-Type: multipart/form-data
Max file size: 10 MB
Max files per request: 10
Allowed types: image/jpeg, image/png, image/webp, application/pdf

Field names map to document types. Use descriptive names: front, back, selfie, liveness. The field name is stored as document.type and used to infer which processing step to run.

The verification must be in PENDING or IN_PROGRESS status.

curl -X POST "https://api.verifymx.xyz/v1/verifications/ver_01HXYZ.../inputs" \
  -H "Authorization: Bearer $TOKEN" \
  -F "front=@ine_front.jpg;type=image/jpeg" \
  -F "back=@ine_back.jpg;type=image/jpeg" \
  -F "selfie=@selfie.jpg;type=image/jpeg"

Response 202:

{
  "uploaded": [
    { "id": "doc_01...", "type": "front",  "mimeType": "image/jpeg" },
    { "id": "doc_02...", "type": "back",   "mimeType": "image/jpeg" },
    { "id": "doc_03...", "type": "selfie", "mimeType": "image/jpeg" }
  ],
  "message": "Documents queued for processing"
}

Processing is asynchronous. Subscribe to webhooks or poll GET /v1/verifications/:id for results.


Workflows

GET /v1/workflows

List all workflows for your organization.

Auth required: Yes

curl "https://api.verifymx.xyz/v1/workflows" \
  -H "Authorization: Bearer $TOKEN"

Response 200:

{
  "workflows": [
    {
      "id": "wf_01HXYZ...",
      "name": "INE + Selfie",
      "isActive": true,
      "steps": [
        { "type": "DOCUMENT_OCR", "order": 0 },
        { "type": "FACE_MATCH",   "order": 1 }
      ],
      "createdAt": "2026-04-08T12:00:00.000Z"
    }
  ]
}

POST /v1/workflows

Create a new workflow. Requires ADMIN or OWNER role.

Auth required: Yes (ADMIN+)

Request body:

FieldTypeRequiredDescription
namestringYes1–200 chars
descriptionstringNoUp to 1000 chars
stepsarrayYesAt least one step (see step schema below)

Step object:

FieldTypeRequiredDescription
typestringYesDOCUMENT_OCR, FACE_MATCH, LIVENESS, or GOVERNMENT_VERIFY
orderintegerYesExecution order (0-indexed)
configobjectNoStep-specific configuration options
curl -X POST "https://api.verifymx.xyz/v1/workflows" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Full KYC",
    "description": "INE OCR + liveness + government verify",
    "steps": [
      { "type": "DOCUMENT_OCR",      "order": 0 },
      { "type": "LIVENESS",          "order": 1 },
      { "type": "FACE_MATCH",        "order": 2 },
      { "type": "GOVERNMENT_VERIFY", "order": 3 }
    ]
  }'

Response 201:

{
  "workflow": {
    "id": "wf_01HXYZ...",
    "name": "Full KYC",
    "isActive": true,
    "steps": [...],
    "createdAt": "2026-04-08T12:00:00.000Z"
  }
}

GET /v1/workflows/:id

Get a single workflow by ID.

Auth required: Yes

curl "https://api.verifymx.xyz/v1/workflows/wf_01HXYZ..." \
  -H "Authorization: Bearer $TOKEN"

Response 200: same shape as the item in the list endpoint.


PATCH /v1/workflows/:id

Update workflow name, description, or steps. Requires ADMIN or OWNER role.

Auth required: Yes (ADMIN+)

Request body: same fields as POST, all optional.

curl -X PATCH "https://api.verifymx.xyz/v1/workflows/wf_01HXYZ..." \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Full KYC v2"}'

Response 200: updated workflow object.


DELETE /v1/workflows/:id

Soft-deletes the workflow (isActive set to false). Existing verifications are unaffected. New verifications cannot be created against an inactive workflow. Requires ADMIN or OWNER role.

Auth required: Yes (ADMIN+)

curl -X DELETE "https://api.verifymx.xyz/v1/workflows/wf_01HXYZ..." \
  -H "Authorization: Bearer $TOKEN"

Response 204: empty body.


Webhooks

GET /v1/webhooks

List all webhook endpoints. Signing secrets are never returned in list responses.

Auth required: Yes

curl "https://api.verifymx.xyz/v1/webhooks" \
  -H "Authorization: Bearer $TOKEN"

Response 200:

{
  "webhooks": [
    {
      "id": "whe_01HXYZ...",
      "url": "https://myapp.mx/webhooks/verifymx",
      "events": ["verification.approved", "verification.rejected"],
      "isActive": true,
      "createdAt": "2026-04-08T12:00:00.000Z"
    }
  ]
}

POST /v1/webhooks

Register a new webhook endpoint. Requires ADMIN or OWNER role. The signing secret is shown once in the response — store it immediately.

Auth required: Yes (ADMIN+)

Request body:

FieldTypeRequiredDescription
urlstringYesHTTPS endpoint URL (max 2048 chars)
eventsarrayYesEvent types to subscribe to (at least one)
secretstringNoCustom signing secret (16–256 chars). Auto-generated if omitted.

Valid event types: verification.created, verification.completed, verification.approved, verification.rejected, verification.manual_review, verification.updated, step.completed, step.failed

curl -X POST "https://api.verifymx.xyz/v1/webhooks" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://myapp.mx/webhooks/verifymx",
    "events": ["verification.approved", "verification.rejected", "verification.manual_review"]
  }'

Response 201:

{
  "webhook": {
    "id": "whe_01HXYZ...",
    "url": "https://myapp.mx/webhooks/verifymx",
    "events": ["verification.approved", "verification.rejected", "verification.manual_review"],
    "secret": "a1b2c3d4e5f6...",
    "isActive": true,
    "createdAt": "2026-04-08T12:00:00.000Z"
  }
}

DELETE /v1/webhooks/:id

Remove a webhook endpoint. Requires ADMIN or OWNER role.

Auth required: Yes (ADMIN+)

curl -X DELETE "https://api.verifymx.xyz/v1/webhooks/whe_01HXYZ..." \
  -H "Authorization: Bearer $TOKEN"

Response 204: empty body.


POST /v1/webhooks/:id/test

Send a synthetic verification.created test payload to the endpoint to verify your handler is reachable and processing signatures correctly.

Auth required: Yes

curl -X POST "https://api.verifymx.xyz/v1/webhooks/whe_01HXYZ.../test" \
  -H "Authorization: Bearer $TOKEN"

Response 200:

{ "delivered": true }

OTP

OTP endpoints are unauthenticated. They are called directly from the end-user's browser during a live verification flow, using verificationId as the capability token.

POST /v1/otp/send

Generate and deliver a 6-digit OTP via SMS or email.

Rate limit: 3 sends per destination per 10 minutes.

Auth required: No

Request body:

FieldTypeRequiredDescription
verificationIdstring (UUID)YesActive verification session ID
channelstringYes"sms" or "email"
destinationstringYesPhone number (E.164) or email address
curl -X POST "https://api.verifymx.xyz/v1/otp/send" \
  -H "Content-Type: application/json" \
  -d '{
    "verificationId": "ver_01HXYZ...",
    "channel": "sms",
    "destination": "+525512345678"
  }'

Response 200:

{
  "sent": true,
  "channel": "sms",
  "expiresIn": 300
}

POST /v1/otp/verify

Verify a 6-digit code submitted by the user. Codes expire after 5 minutes. After 5 failed attempts, the code is invalidated.

Auth required: No

Request body:

FieldTypeRequiredDescription
verificationIdstring (UUID)YesSame session used when sending
channelstringYes"sms" or "email"
codestringYesExactly 6 digits
curl -X POST "https://api.verifymx.xyz/v1/otp/verify" \
  -H "Content-Type: application/json" \
  -d '{
    "verificationId": "ver_01HXYZ...",
    "channel": "sms",
    "code": "847291"
  }'

Response 200:

{
  "valid": true,
  "attemptsRemaining": 5
}

If valid is false and attemptsRemaining is 0, the code has been invalidated — call /v1/otp/send again.


AML

POST /v1/aml/screen

Screen a full name against AML watchlists. Returns matching entries with similarity scores. Uses fuzzy matching to handle name variations and transliterations.

Auth required: Yes

Request body:

FieldTypeRequiredDescription
fullNamestringYes2–500 chars
fuzzyThresholdnumberNoSimilarity threshold 0–1 (default: 0.75)
listsstring[]NoWatchlist IDs to search. Omit to search all.
countrystringNoISO 3166-1 alpha-2 country code to narrow results
curl -X POST "https://api.verifymx.xyz/v1/aml/screen" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "fullName": "Juan García López",
    "fuzzyThreshold": 0.8,
    "country": "MX"
  }'

Response 200:

{
  "matches": [
    {
      "list": "OFAC_SDN",
      "name": "JUAN GARCIA LOPEZ",
      "score": 0.98,
      "entityType": "individual",
      "country": "MX"
    }
  ],
  "totalMatches": 1,
  "screened": true
}

GET /v1/aml/lists

List available watchlists with entry counts.

Auth required: Yes

curl "https://api.verifymx.xyz/v1/aml/lists" \
  -H "Authorization: Bearer $TOKEN"

Response 200:

{
  "lists": [
    { "id": "OFAC_SDN",   "name": "OFAC Specially Designated Nationals", "entries": 18432 },
    { "id": "UN_SC",      "name": "UN Security Council Consolidated List",  "entries": 4201 },
    { "id": "EU_CONSOL",  "name": "EU Consolidated Sanctions List",         "entries": 2918 },
    { "id": "MX_SAT_69B", "name": "SAT Article 69-B (Mexico)",              "entries": 1024 }
  ]
}

POST /v1/aml/monitor

Enable or disable daily re-screening for a verification. When enabled, the name is re-screened every 24 hours for 90 days; if new matches appear, a webhook fires.

Auth required: Yes

Request body:

FieldTypeRequiredDescription
verificationIdstringYesVerification to monitor
fullNamestringYesName to re-screen daily
enabledbooleanYestrue to enable, false to disable
curl -X POST "https://api.verifymx.xyz/v1/aml/monitor" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "verificationId": "ver_01HXYZ...",
    "fullName": "Juan García López",
    "enabled": true
  }'

Response 200:

{
  "verificationId": "ver_01HXYZ...",
  "monitoring": true,
  "message": "Daily AML monitoring enabled"
}

Organizations

GET /v1/organizations/me

Get current organization details.

Auth required: Yes

curl "https://api.verifymx.xyz/v1/organizations/me" \
  -H "Authorization: Bearer $TOKEN"

Response 200:

{
  "org": {
    "id": "org_01HXYZ...",
    "name": "Acme Corp",
    "slug": "acme-corp",
    "dataRetentionDays": 365,
    "createdAt": "2026-04-08T12:00:00.000Z"
  }
}

PATCH /v1/organizations/me

Update organization settings. Requires ADMIN or OWNER role.

Auth required: Yes (ADMIN+)

Request body:

FieldTypeRequiredDescription
namestringNo2–100 chars
dataRetentionDaysintegerNo30–3650 days
curl -X PATCH "https://api.verifymx.xyz/v1/organizations/me" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"dataRetentionDays": 730}'

Response 200: updated org object.


GET /v1/organizations/me/api-keys

List all API keys for your organization. Key hashes are never returned.

Auth required: Yes

curl "https://api.verifymx.xyz/v1/organizations/me/api-keys" \
  -H "Authorization: Bearer $TOKEN"

Response 200:

{
  "apiKeys": [
    {
      "id": "key_01HXYZ...",
      "name": "Default key",
      "prefix": "vmx_a1b2c3d4",
      "rateLimit": 10,
      "isActive": true,
      "lastUsedAt": "2026-04-08T11:59:00.000Z",
      "createdAt": "2026-04-08T12:00:00.000Z"
    }
  ]
}

POST /v1/organizations/me/api-keys

Create a new API key. Requires ADMIN or OWNER role. The raw key is returned once.

Auth required: Yes (ADMIN+)

Request body:

FieldTypeRequiredDescription
namestringYes1–100 chars
rateLimitintegerNoRequests per second (0–1000, default 10)
curl -X POST "https://api.verifymx.xyz/v1/organizations/me/api-keys" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Production key", "rateLimit": 50}'

Response 201:

{
  "apiKey": {
    "id": "key_02HXYZ...",
    "name": "Production key",
    "key": "vmx_x9y8z7w6...",
    "prefix": "vmx_x9y8z7w6",
    "rateLimit": 50,
    "createdAt": "2026-04-08T12:00:00.000Z"
  }
}

DELETE /v1/organizations/me/api-keys/:id

Revoke an API key. Requires ADMIN or OWNER role. You cannot revoke the key currently authenticating the request.

Auth required: Yes (ADMIN+)

curl -X DELETE "https://api.verifymx.xyz/v1/organizations/me/api-keys/key_02HXYZ..." \
  -H "Authorization: Bearer $TOKEN"

Response 204: empty body.


System

GET /v1/openapi.json

Returns the full OpenAPI 3.1 specification generated from all registered routes.

Auth required: No
Rate limit: 60 req/min (relaxed for CI tooling)

curl "https://api.verifymx.xyz/v1/openapi.json" | jq '.paths | keys'

GET /health

Health check. Returns 200 when the server is ready; checks Redis connectivity.

Auth required: No

curl "https://api.verifymx.xyz/health"

Response 200:

{
  "status": "ok",
  "uptime": 3600.42,
  "timestamp": "2026-04-08T12:00:00.000Z",
  "redis": "ok"
}

On this page