Skip to content

Moonlit API Reference

Base URL (production): https://api.moonlit.io
Base URL (local dev): http://localhost:5500
API Version: v1
Content-Type: application/json

Authentication

All endpoints except those marked Public require a Bearer JWT token issued by your GoTrue auth instance.

http
Authorization: Bearer <your-jwt-token>

Obtain a token by authenticating through the GoTrue endpoint:

http
POST /auth/v1/token?grant_type=password
Content-Type: application/json

{
  "email": "you@company.com",
  "password": "your-password"
}

Health

GET /health

Check system status. No authentication required.

Response 200 OK

Healthy

Companies

POST /api/v1/companies — Public

Register a new company. Called once during initial account setup.

Request body

json
{
  "companyName": "Acme Corp",
  "industry": "Technology",
  "country": "US",
  "registrationNumber": "12345678"
}

Response 201 Created

json
{
  "id": "c9faf6b6-cc99-4351-988a-5695015769c2",
  "companyName": "Acme Corp",
  "industry": "Technology",
  "country": "US",
  "registrationNumber": "12345678",
  "createdAt": "2025-01-15T10:00:00Z"
}

GET /api/v1/companies/me — Auth Required

Get the authenticated company's profile.

Response 200 OK

json
{
  "id": "c9faf6b6-cc99-4351-988a-5695015769c2",
  "companyName": "Acme Corp",
  "industry": "Technology",
  "country": "US",
  "contactEmail": "hr@acme.com",
  "memberCount": 3,
  "createdAt": "2025-01-01T00:00:00Z"
}

PATCH /api/v1/companies/me — Auth Required

Update company profile.

Request body

json
{
  "companyId": "c9faf6b6-cc99-4351-988a-5695015769c2",
  "companyName": "Acme Corporation",
  "contactEmail": "compliance@acme.com"
}

Response 200 OK — Updated company object


POST /api/v1/companies/me/members — Auth Required

Invite a team member to your company account.

Request body

json
{
  "email": "newmember@acme.com",
  "fullName": "Jane Smith",
  "role": "member"
}

Roles: member, admin

Response 201 Created — Invitation sent; member receives email to complete sign-up


Employees

GET /api/v1/employees — Auth Required

List all registered employees for the authenticated company.

Query parameters

ParameterTypeDefaultDescription
pageinteger1Page number
pageSizeinteger20Results per page (max 100)
searchstringSearch by name, MoonlitID, or country

Response 200 OK

json
{
  "data": [
    {
      "id": "emp-uuid",
      "moonlitId": "MLT-abc123",
      "fullName": "John Smith",
      "jobTitle": "Senior Engineer",
      "department": "Engineering",
      "country": "US",
      "employmentStartDate": "2024-01-15",
      "isActive": true,
      "createdAt": "2025-01-01T00:00:00Z"
    }
  ],
  "total": 150,
  "page": 1,
  "pageSize": 20
}

POST /api/v1/employees — Auth Required

Register a new employee. All PII fields are hashed before storage.

Request body

json
{
  "companyId": "c9faf6b6-cc99-4351-988a-5695015769c2",
  "nationalId": "US-001-JOHNDOE",
  "primaryEmail": "john@acme.com",
  "fullName": "John Smith",
  "employmentStartDate": "2025-01-01",
  "jobTitle": "Senior Engineer",
  "department": "Engineering"
}

Response 201 Created — Employee object with assigned MoonlitID


POST /api/v1/employees/import — Auth Required

Bulk import employees from CSV.

Requestmultipart/form-data

FieldTypeDescription
filefileCSV file with headers: nationalId, primaryEmail, fullName, employmentStartDate, jobTitle, department

Response 202 Accepted

json
{
  "jobId": "import-job-uuid",
  "status": "processing",
  "totalRows": 250
}

POST /api/v1/employees/merge — Auth Required

Merge two duplicate employee records into a single canonical record.

Request body

json
{
  "primaryEmployeeId": "emp-uuid-1",
  "duplicateEmployeeId": "emp-uuid-2"
}

Response 200 OK — Merged employee object


DELETE /api/v1/employees/{id} — Auth Required

Deactivate (soft-delete) an employee. The hashed record is retained.

Path parameters

ParameterTypeDescription
idUUIDEmployee ID

Response 204 No Content


Scans

POST /api/v1/scans/pre-hire — Auth Required

Run a pre-hire conflict scan against a candidate.

Request body

json
{
  "requestingCompanyId": "c9faf6b6-cc99-4351-988a-5695015769c2",
  "candidateNationalId": "US-002-JANEDOE",
  "candidateEmail": "jane.doe@example.com",
  "candidateFullName": "Jane Doe",
  "idDocumentType": "NationalId",
  "issuingCountry": "US"
}

idDocumentType values: NationalId, Passport, DriversLicense, ResidencePermit

Response 200 OK

json
{
  "scanId": "scan-uuid",
  "conflictFound": true,
  "trustScore": 35,
  "trustTier": "Low",
  "summary": "Active employment conflict detected",
  "signals": [
    {
      "description": "Active employment detected at another Moonlit member company",
      "severity": "Critical",
      "isActiveConflict": true
    }
  ],
  "completedAt": "2025-01-15T10:30:01Z"
}

Trust tiers

TierScore RangeRecommendation
High85–100Proceed with hire
Medium50–84Review manually
Low0–49Escalate; do not proceed without investigation

POST /api/v1/scans/consent — Auth Required

Record explicit background scan consent from a candidate.

Request body

json
{
  "companyId": "company-uuid",
  "candidateEmail": "jane.doe@example.com",
  "consentTimestamp": "2025-01-15T09:00:00Z",
  "consentIpAddress": "203.0.113.10"
}

Response 201 Created — Consent record


GET /api/v1/scans — Auth Required

Get scan history for the authenticated company.

Query parameters

ParameterTypeDefaultDescription
pageinteger1Page number
pageSizeinteger20Results per page
fromISO dateFilter from date
toISO dateFilter to date

Response 200 OK — Paginated list of scan summaries


PATCH /api/v1/scans/{id}/resolve — Auth Required

Mark a scan conflict as a resolved false positive.

Path parameters

ParameterTypeDescription
idUUIDScan ID

Request body

json
{
  "resolutionNote": "Investigated — candidate's previous employment ended before our start date; registration overlap was a data entry delay."
}

Response 200 OK — Updated scan object


Alerts

GET /api/v1/alerts — Auth Required

List conflict alerts for the authenticated company.

Query parameters

ParameterTypeDefaultDescription
pageinteger1Page number
pageSizeinteger20Results per page

Response 200 OK

json
{
  "data": [
    {
      "id": "alert-uuid",
      "moonlitId": "MLT-abc123",
      "title": "Dual Employment Conflict",
      "description": "Active employee detected at another Moonlit member company",
      "severity": "Critical",
      "isRead": false,
      "createdAt": "2025-01-15T10:30:00Z"
    }
  ],
  "total": 3,
  "page": 1,
  "pageSize": 20
}

PATCH /api/v1/alerts/{id}/read — Auth Required

Mark an alert as read.

Path parameters

ParameterTypeDescription
idUUIDAlert ID

Response 200 OK — Updated alert object


Dashboard

GET /api/v1/dashboard/stats — Auth Required

Retrieve key performance indicators for the dashboard.

Response 200 OK

json
{
  "totalEmployees": 150,
  "activeConflicts": 3,
  "scansThisMonth": 42,
  "avgTrustScore": 87.4,
  "recentActivity": [
    {
      "id": "alert-uuid",
      "title": "Dual Employment Conflict",
      "description": "Jane Doe flagged",
      "moonlitId": "MLT-abc123",
      "severity": "Critical",
      "createdAt": "2025-01-15T10:30:00Z"
    }
  ]
}

Webhooks

GET /api/v1/webhooks — Auth Required

List all webhook subscriptions for the company.

Response 200 OK — Array of webhook subscription objects


POST /api/v1/webhooks — Auth Required

Create a new webhook subscription.

Request body

json
{
  "url": "https://yourapp.com/webhooks/moonlit",
  "eventType": "ConflictDetected",
  "secret": "your-signing-secret"
}

Event types: ConflictDetected, AlertGenerated, ScanCompleted, EmployeeRegistered, AllEvents

Response 201 Created — Webhook subscription object including the assigned id


DELETE /api/v1/webhooks/{id} — Auth Required

Delete a webhook subscription.

Response 204 No Content


GET /api/v1/webhooks/event-types — Public

List all supported webhook event types.

Response 200 OK

json
["ConflictDetected", "AlertGenerated", "ScanCompleted", "EmployeeRegistered", "AllEvents"]

Billing

GET /api/v1/billing/plans — Public

List available subscription plans.

Response 200 OK

json
[
  {
    "id": "plan-starter",
    "name": "Starter",
    "monthlyPrice": 49,
    "annualPrice": 470,
    "maxEmployees": 50,
    "scansPerMonth": 20,
    "webhooksEnabled": false,
    "apiAccess": false
  },
  {
    "id": "plan-growth",
    "name": "Growth",
    "monthlyPrice": 149,
    "annualPrice": 1430,
    "maxEmployees": 500,
    "scansPerMonth": 200,
    "webhooksEnabled": true,
    "apiAccess": true
  }
]

GET /api/v1/billing/current — Auth Required

Get the current subscription plan and usage.

Response 200 OK — Active subscription with usage counters


POST /api/v1/billing/checkout — Auth Required

Create a Stripe checkout session to start or upgrade a subscription.

Request body

json
{
  "planId": "plan-growth",
  "interval": "monthly"
}

Response 200 OK

json
{
  "checkoutUrl": "https://checkout.stripe.com/pay/cs_..."
}

POST /api/v1/billing/portal — Auth Required

Create a Stripe customer portal session for self-service billing management.

Response 200 OK

json
{
  "portalUrl": "https://billing.stripe.com/session/..."
}

GET /api/v1/billing/invoices — Auth Required

Get the company's invoice history.

Response 200 OK — Array of invoice objects with amount, date, and download URL


POST /api/v1/billing/initiate — Auth Required

Start a payment flow for non-Stripe providers.

Request body

json
{
  "provider": "Paymob",
  "planId": "plan-growth",
  "interval": "monthly"
}

Providers: Stripe, Paymob, Fawry, Manual


GET /api/v1/billing/status/{id} — Auth Required

Get the status of a payment transaction.


POST /api/v1/billing/subscription/cancel — Auth Required

Cancel the active subscription at the end of the billing period.


Errors

All error responses follow this structure:

json
{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
  "title": "Not Found",
  "status": 404,
  "detail": "Employee with ID 'emp-xyz' was not found",
  "traceId": "00-abc123-def456-00"
}
StatusMeaning
400Bad request — validation error in request body
401Unauthorized — missing or invalid JWT
403Forbidden — authenticated but insufficient permissions
404Not found — resource does not exist
422Unprocessable entity — business logic violation
429Too many requests — rate limit exceeded
500Internal server error — contact support

Rate Limits

TierLimit
Default100 requests / minute / user
Bulk import5 requests / minute / company

When rate limited, the response includes:

HTTP/1.1 429 Too Many Requests
Retry-After: 30

Privacy-first employment integrity monitoring.