API Reference

Midnight Auth REST API v1

Authentication

All API requests require an API key sent via the X-API-Key header. API keys are generated in the Console under Apps.

// Every request must include:
X-API-Key: ma_your_api_key_here
Content-Type: application/json
ParamTypeRequiredDescription
X-API-KeystringYesPartner app API key (starts with "ma_")
Content-TypestringYesMust be "application/json" for POST/PUT/PATCH

Security Notes

  • API keys are scoped per partner app — never share across apps
  • Keys can be rotated in the Console without downtime
  • All requests must use HTTPS in production
  • Rate limit: 100 requests/minute per API key
  • No PII is stored or returned — only derived attributes and booleans

Health Check

GET/health

Check if the API is reachable and healthy. Does not require authentication.

Response 200

{
  "status": "ok",
  "service": "midnight-auth-api",
  "timestamp": "2026-02-26T10:00:00.000Z",
  "uptime": 86400
}

Login Endpoints

The login flow lets partner apps authenticate users via their Veridian wallet. Your app initiates a session, displays a QR code, and polls until the wallet approves.

POST/login/initiate

Start a new login session. Returns a QR code and deep link for the user's wallet.

Request Body

{
  "apiKey": "ma_your_api_key_here",
  "requirements": ["email", "phone"],
  "redirectUrl": "https://myapp.com/dashboard"  // optional
}
ParamTypeRequiredDescription
apiKeystringYesPartner app API key
requirementsstring[]YesCredential types: "email", "phone", "social", "humanity", "age"
redirectUrlstringNoURL to redirect user after login

Response 201

{
  "sessionId": "ses_a1b2c3d4...",
  "challengeNonce": "n_x7y8z9...",
  "deepLink": "midnight-auth://login?session=ses_a1b2c3d4...",
  "qrCodeUrl": "data:image/png;base64,iVBOR...",
  "expiresAt": "2026-02-26T10:05:00.000Z",
  "appName": "My App",
  "requirements": ["email", "phone"]
}
GET/login/status/:sessionId

Check the current status of a login session. Use for polling.

Response 200

{
  "sessionId": "ses_a1b2c3d4...",
  "status": "pending",   // "pending" | "presented" | "verified" | "expired" | "denied"
  "requirements": ["email", "phone"],
  "expiresAt": "2026-02-26T10:05:00.000Z"
}
ParamTypeRequiredDescription
pendingstatusWaiting for user to scan QR code
presentedstatusUser scanned, presenting credentials
verifiedstatusAll credentials verified — call /login/result
expiredstatusSession expired (5 min TTL)
deniedstatusUser denied the login request
GET/login/result/:sessionId

Fetch the verified login result. Only returns data when status is "verified".

Response 200

{
  "verified": true,
  "userId": "usr_7a8b3c...",
  "verifiedAt": "2026-02-26T10:01:30.000Z",

  "emailVerified": true,
  "emailDomain": "gmail.com",
  "domainType": "consumer",

  "phoneVerified": true,
  "phoneCountry": "US",
  "carrierType": "mobile",

  "hasSocial": true,
  "socialPlatform": "twitter",
  "accountAgeMonths": 36,
  "followerRange": "1k-10k",

  "isHuman": true,
  "humanityScore": 0.98,

  "over18": true,
  "over21": true,
  "over25": false
}

Onboarding Endpoints

The onboarding flow issues verifiable ACDC credentials to users' Veridian wallets. Each verification step issues a separate credential.

POST/onboard/connect

Connect a Veridian wallet to start the onboarding session.

Request Body

{
  "userAid": "D_keri_aid_44_chars..."
}

Response 201

{
  "sessionId": "ses_x1y2z3...",
  "connectedAt": "2026-02-26T10:00:00.000Z",
  "expiresAt": "2026-02-26T10:30:00.000Z"
}
POST/onboard/email/send

Send a 6-digit verification code to the user's email. The email is hashed and never stored.

Request Body

{
  "sessionId": "ses_x1y2z3...",
  "email": "user@example.com"
}

Response 200

{
  "message": "Verification code sent",
  "domain": "consumer",
  "expiresInMinutes": 10
}
POST/onboard/email/verify

Verify the email code. On success, issues an EmailVerificationCredential ACDC.

Request Body

{
  "sessionId": "ses_x1y2z3...",
  "code": "123456"
}

Response 200

{
  "credentialType": "EmailVerificationCredential",
  "schemaSaid": "EBfd...",
  "issuedAt": "2026-02-26T10:02:00.000Z",
  "commitmentHash": "0x7a8b...",
  "trustLevel": 1,
  "message": "Email verified, credential issued"
}
POST/onboard/phone/send

Send a verification code to the user's phone via WhatsApp/SMS. Number is never stored.

Request Body

{
  "sessionId": "ses_x1y2z3...",
  "phoneNumber": "+1234567890"
}

Response 200

{
  "message": "Verification code sent",
  "expiresInMinutes": 10
}
POST/onboard/phone/verify

Verify the phone code. On success, issues a PhoneVerificationCredential ACDC.

Request Body

{
  "sessionId": "ses_x1y2z3...",
  "code": "654321"
}

Response 200

{
  "credentialType": "PhoneVerificationCredential",
  "schemaSaid": "EBfe...",
  "issuedAt": "2026-02-26T10:03:00.000Z",
  "commitmentHash": "0x9c0d...",
  "trustLevel": 2,
  "message": "Phone verified, credential issued"
}
GET/onboard/social/:provider

Start OAuth flow for a social provider. Provider must be "twitter", "discord", or "github".

Query Parameters

ParamTypeRequiredDescription
sessionIdstringYesActive onboarding session ID

Response 200

{
  "authorizationUrl": "https://twitter.com/i/oauth2/authorize?...",
  "state": "st_abc123..."
}
GET/onboard/social/callback

Handle OAuth callback. On success, issues a SocialAccountCredential ACDC.

Query Parameters

ParamTypeRequiredDescription
providerstringYesOAuth provider name
codestringYesAuthorization code from OAuth redirect
statestringYesState parameter from the initial redirect

Response 200

{
  "credentialType": "SocialAccountCredential",
  "schemaSaid": "EBff...",
  "issuedAt": "2026-02-26T10:04:00.000Z",
  "commitmentHash": "0xab12...",
  "trustLevel": 3,
  "message": "Social account verified, credential issued"
}
POST/onboard/humanity

Verify humanity with a captcha token. On success, issues a HumanityCredential ACDC.

Request Body

{
  "sessionId": "ses_x1y2z3...",
  "captchaToken": "hcaptcha_response_token_here"
}

Response 200

{
  "credentialType": "HumanityCredential",
  "schemaSaid": "EBfg...",
  "issuedAt": "2026-02-26T10:05:00.000Z",
  "commitmentHash": "0xcd34...",
  "trustLevel": 4,
  "message": "Humanity verified, credential issued"
}
GET/onboard/session/:sessionId

Get the current status of an onboarding session.

Response 200

{
  "sessionId": "ses_x1y2z3...",
  "userAid": "D_keri_aid_44_chars...",
  "completedSteps": ["email", "phone"],
  "trustLevel": 2,
  "expiresAt": "2026-02-26T10:30:00.000Z"
}

Error Responses

All errors return a consistent JSON structure. Error messages never contain PII.

{
  "statusCode": 403,
  "message": "Invalid or inactive API key",
  "error": "Forbidden"
}
ParamTypeRequiredDescription
400Bad RequestMissing or invalid request parameters
401UnauthorizedMissing API key header
403ForbiddenInvalid or inactive API key
404Not FoundSession or resource not found
409ConflictDuplicate operation (e.g. code already sent)
429Too Many RequestsRate limit exceeded (100 req/min)
500Internal Server ErrorServer error — retry with backoff

CORS & Production Requirements

  • HTTPS is required for all production API calls
  • CORS is configured per partner app in the Console
  • API keys must be kept server-side — never expose in client code
  • All PII (email, phone) is hashed on receipt and never stored in plain text
  • Session tokens expire after 5 minutes (login) or 30 minutes (onboarding)
  • Rate limiting: 100 requests/minute per API key, 429 returned when exceeded