LealUp Docs
API & Webhooks

Endpoints

Quick reference of resources — customers, health, playbooks, tasks, users, analytics.

Quick reference for the main endpoints. The complete, authoritative spec is at api.lealup.com/v1/openapi.json.

Conventions

  • All paths are prefixed with /v1/.
  • All require Authorization: Bearer {token} — see Authentication.
  • POST/PATCH expect Content-Type: application/json.
  • Responses are JSON (application/json) or application/problem+json on errors.
  • Pagination: cursor in query cursor=..., returns next_cursor and has_more.

Customers

GET /v1/customers

List customers.

Query params:

  • statusactive | trial | churned | paused (multiple comma-separated).
  • owner_id — filter by owner.
  • segment_id — filter by segment.
  • health_statushealthy | neutral | at_risk.
  • min_arr, max_arr — ARR range.
  • renewal_within_days30, 60, 90.
  • q — full-text search on name, email_domain.
  • limit — default 50, max 200.
  • cursor — pagination.

Response:

{
  "data": [{"id": "...", "name": "...", ...}],
  "next_cursor": "eyJ...",
  "has_more": true
}

POST /v1/customers

Create a customer.

Body:

{
  "name": "Acme Corp",
  "email_domain": "acme.com",
  "arr": 145000,
  "owner_email": "[email protected]",
  "status": "active",
  "renewal_date": "2026-06-15",
  "custom_fields": {
    "tier": "Platinum",
    "industry": "Fintech"
  }
}

Recommended headers: Idempotency-Key.

Response: 201 Created with the full customer (includes id, tenant_id, created_at).

GET /v1/customers/{id}

Customer detail. Includes current health score, renewal info, pending tasks.

PATCH /v1/customers/{id}

Partial update. Only the fields sent are updated.

{
  "arr": 180000,
  "custom_fields": {
    "tier": "Platinum"
  }
}

DELETE /v1/customers/{id}

Soft-delete. Reversible for 30 days via Admin → Customers → Trash.

For hard-delete (GDPR): DELETE /v1/customers/{id}?hard=true — requires scope admin:*.

POST /v1/customers/bulk-import

Bulk import (equivalent to CSV via UI).

Body:

{
  "customers": [/* up to 1000 */],
  "mode": "create_or_update",
  "conflict_resolution": "skip"
}

Returns job_id — poll at GET /v1/jobs/{job_id}.

Contacts

GET /v1/customers/{id}/contacts

List a customer's contacts.

POST /v1/customers/{id}/contacts

Create a contact.

{
  "name": "Maria Perez",
  "email": "[email protected]",
  "title": "VP Operations",
  "is_champion": true,
  "whatsapp_number": "+56911112222"
}

Health

GET /v1/customers/{id}/health

Current score + breakdown by dimension.

GET /v1/customers/{id}/health/history

Time series (default last 90 days).

Query: from, to (ISO 8601), granularity (daily default, weekly).

POST /v1/customers/{id}/health/recalculate

Force immediate recalculation (useful after batch-importing events).

GET /v1/health/model

Returns the health model configuration (weights, thresholds, active dimensions).

PATCH /v1/health/model

Update the model. Requires scope health:write + admin role.

Playbooks and tasks

GET /v1/playbooks

List playbooks.

POST /v1/playbooks

Create a playbook.

{
  "name": "Enterprise At-Risk",
  "trigger": {
    "type": "condition",
    "expression": "health_score < 60 AND segment == 'Enterprise'"
  },
  "actions": [
    {"type": "create_task", "title": "Call champion", "assignee": "owner"},
    {"type": "notify", "channel": "slack", "target": "#cs-alerts"}
  ],
  "enabled": true
}

GET /v1/playbooks/{id}/runs

Run history (which customers matched, which actions fired).

GET /v1/tasks

Tasks.

Query:

  • owner_id, assignee_id
  • statuspending | in_progress | completed | skipped.
  • due_before, due_after — ISO dates.
  • customer_id, playbook_id.

PATCH /v1/tasks/{id}

Update status or reassignment.

POST /v1/tasks/{id}/complete

Complete with structured outcome.

{
  "outcome": "resolved",
  "notes": "Champion confirmed the usage drop was due to an internal deployment, not a churn signal"
}

Users

GET /v1/users

List users (admin only).

GET /v1/users/me

Current actor's profile (user or api_key).

POST /v1/users

Invite a user (admin only, scope users:write).

{
  "email": "[email protected]",
  "role": "csm",
  "locale": "es",
  "timezone": "America/Santiago",
  "initial_territory_ids": ["..."]
}

PATCH /v1/users/{id}/role

Change role. Admin only.

Analytics

GET /v1/analytics/portfolio

Portfolio summary: total ARR, customers by status, health distribution, top risks.

GET /v1/analytics/renewals

Renewal pipeline by month.

Query: from, to, forecast_category.

GET /v1/analytics/cohorts

Retention cohort analysis.

Query: cohort_periodmonth | quarter. months_out — default 12.

Events (ingestion)

See Event ingestion for the full treatment.

POST /v1/events

Batch event ingestion.

{
  "events": [/* up to 1000 */]
}

GET /v1/events

Event query (debugging). Rate-limited.

Webhooks (outbound)

See Webhooks.

GET /v1/webhook-subscriptions

POST /v1/webhook-subscriptions

DELETE /v1/webhook-subscriptions/{id}

Integrations

GET /v1/integrations

Status of integrations (Gmail, Calendar, Slack, etc.).

POST /v1/integrations/{name}/connect

Start OAuth flow. Returns authorization URL.

DELETE /v1/integrations/{name}

Disconnect.

Rate limits

Per account, per endpoint class:

Endpoint classRate limit (default)
Reads (GET)1000 req/min
Writes (POST, PATCH, DELETE)300 req/min
Ingestion (POST /v1/events)60 batches/min (up to 1000 events each)
Bulk (POST /v1/customers/bulk-import)10 req/hour

Headers on each response:

  • X-RateLimit-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset (unix timestamp)

If you exceed: 429 Too Many Requests + Retry-After header (seconds).

Scale/Enterprise tiers have higher limits. Contact sales if you need more.

Errors — format (RFC 7807)

{
  "type": "https://api.lealup.com/errors/validation_error",
  "title": "Validation error",
  "status": 400,
  "detail": "Field 'arr' must be a positive number",
  "instance": "/v1/customers",
  "trace_id": "01HXYZ123ABC",
  "errors": [
    {"field": "arr", "code": "must_be_positive", "message": "..."}
  ]
}

Common status codes

  • 200 OK
  • 201 Created
  • 204 No Content (DELETEs)
  • 400 validation error
  • 401 unauthenticated
  • 403 forbidden (scope/permission)
  • 404 not found (also when crossing accounts)
  • 409 conflict (duplicate, invalid state)
  • 422 unprocessable (semantic error, e.g., renewal_date < today)
  • 429 rate limited
  • 500 server error (report with trace_id)

Next

On this page