Skip to content

API Reference ​

Authoritative, always-current reference

The interactive reseller API reference is generated directly from the running API and is always in sync with production. Sign in as a reseller and open:

app.hoppla.dev/api/v1/reseller/docs (reseller login required)

It documents every endpoint with request/response schemas and the exact token scope each one needs. The guide below is a conceptual overview.

Complete reference for the Hoppla reseller API.

Base URL ​

https://app.hoppla.dev/api/v1

Authentication ​

All endpoints require a Bearer token obtained from POST /auth/token.

Authorization: Bearer {token}

Rate Limiting ​

Reseller endpoints are rate-limited to 120 requests per minute. When exceeded, the API returns 429 Too Many Requests with a Retry-After header.

Error Format ​

All error responses follow this structure:

json
{
  "message": "Human-readable error description",
  "errors": {
    "field_name": ["Validation error message"]
  }
}
StatusMeaning
401Missing or invalid authentication token
403Insufficient permissions (not a reseller, or team not managed by you)
404Resource not found
422Validation failed
429Rate limit exceeded

Authentication Endpoints ​

Create Token ​

Obtain an API token by exchanging credentials.

POST /auth/token

Rate limit: throttle:auth (stricter than standard API limit)

Request Body:

FieldTypeRequiredDescription
emailstringyesAccount email address
passwordstringyesAccount password
device_namestringyesIdentifier for the token (e.g. "CI Server")

Example:

bash
curl -X POST https://app.hoppla.dev/api/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "secret",
    "device_name": "Integration"
  }'

Response (201):

json
{
  "token": "ss-aB3dLookup9xKsecretValue1234...",
  "user": {
    "id": "9a1b2c3d-e5f6-...",
    "name": "Reseller Account",
    "email": "[email protected]",
    "tenant_id": "tenant-uuid-...",
    "role": "admin",
    "language": "en",
    "managed_teams": [
      {
        "id": "mt-uuid-...",
        "tenant_id": "managed-tenant-uuid-...",
        "name": "Acme Corp",
        "slug": "acme-corp",
        "label": "Enterprise Client"
      }
    ]
  }
}

For reseller accounts, the response includes a managed_teams array listing all teams you manage.

Token format: All Hoppla tokens are opaque, prefixed strings shown once at creation: session tokens start with ss-, reseller management tokens with rk-, impersonation tokens with ik-, and managed-team service API keys with svc-. Store them securely — they cannot be retrieved again. For server-to-server reseller automation, prefer a dedicated reseller management token (rk-…) over a session token; see Reseller API Tokens.

Get Current User ​

GET /user

Example:

bash
curl https://app.hoppla.dev/api/v1/user \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "id": "9a1b2c3d-e5f6-...",
  "name": "Reseller Account",
  "email": "[email protected]",
  "tenant_id": "tenant-uuid-...",
  "role": "admin",
  "language": "en"
}

Revoke Token ​

DELETE /auth/token

Example:

bash
curl -X DELETE https://app.hoppla.dev/api/v1/auth/token \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "message": "Token revoked"
}

Managed Teams ​

All endpoints under /reseller/managed-teams require the reseller role. You can only access teams that belong to your tenant.

List Managed Teams ​

GET /reseller/managed-teams

Query Parameters:

ParameterTypeDefaultDescription
pageinteger1Page number
per_pageinteger15Items per page

Example:

bash
curl "https://app.hoppla.dev/api/v1/reseller/managed-teams?page=1" \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "data": [
    {
      "id": "mt-uuid-...",
      "tenant_id": "tenant-uuid-...",
      "name": "Acme Corp",
      "slug": "acme-corp",
      "plan": "starter",
      "label": "Enterprise Client #42",
      "monitors_count": 12,
      "monitors_down_count": 1,
      "monitors_up_count": 10,
      "is_paused": false,
      "paused_at": null,
      "paused_reason": null,
      "on_trial": false,
      "trial_ends_at": null,
      "deleted_at": null,
      "days_until_purge": null,
      "created_at": "2026-01-15T10:30:00.000000Z",
      "updated_at": "2026-01-15T10:30:00.000000Z"
    }
  ],
  "links": { "first": "...", "last": "...", "prev": null, "next": null },
  "meta": { "current_page": 1, "last_page": 1, "per_page": 15, "total": 1 }
}

Create Managed Team ​

Provision a new tenant with an optional admin user.

POST /reseller/managed-teams

Request Body:

FieldTypeRequiredDescription
namestringyesTeam display name (max 255 chars)
slugstringyesURL-safe identifier (lowercase, hyphens only, max 100 chars, unique)
planstringnoPlan tier: free, hobby, starter, or pro (default: free)
labelstringnoInternal label for your reference (max 255 chars)
admin_emailstringnoEmail for the team's admin user
admin_namestringnoName for the team's admin user
admin_passwordstringconditionalRequired if admin_email is provided (min 12 chars, checked against breach databases)

If admin_email already belongs to an account, the request is rejected with 422. That person keeps their own password: create the team without admin_email, then invite them as admin. | trial_days | integer | no | Start the team with a trial period (1-365 days). The team's plan applies during the trial and reverts to free when it expires. |

Example:

bash
curl -X POST https://app.hoppla.dev/api/v1/reseller/managed-teams \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp",
    "slug": "acme-corp",
    "plan": "starter",
    "trial_days": 14,
    "label": "Enterprise Client #42",
    "admin_email": "[email protected]",
    "admin_name": "Jane Doe",
    "admin_password": "securepass12345"
  }'

Response (201):

json
{
  "data": {
    "id": "mt-uuid-...",
    "tenant_id": "new-tenant-uuid-...",
    "name": "Acme Corp",
    "slug": "acme-corp",
    "plan": "starter",
    "label": "Enterprise Client #42",
    "monitors_count": 0,
    "monitors_down_count": 0,
    "monitors_up_count": 0,
    "is_paused": false,
    "paused_at": null,
    "paused_reason": null,
    "on_trial": false,
    "trial_ends_at": null,
    "deleted_at": null,
    "days_until_purge": null,
    "created_at": "2026-01-15T10:30:00.000000Z",
    "updated_at": "2026-01-15T10:30:00.000000Z"
  }
}

This also dispatches a team.provisioned webhook event.

Get Managed Team ​

GET /reseller/managed-teams/{id}

Example:

bash
curl https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-... \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "data": {
    "id": "mt-uuid-...",
    "tenant_id": "tenant-uuid-...",
    "name": "Acme Corp",
    "slug": "acme-corp",
    "plan": "starter",
    "label": "Enterprise Client #42",
    "monitors_count": 12,
    "monitors_down_count": 1,
    "monitors_up_count": 10,
    "is_paused": false,
    "paused_at": null,
    "paused_reason": null,
    "on_trial": false,
    "trial_ends_at": null,
    "deleted_at": null,
    "days_until_purge": null,
    "created_at": "2026-01-15T10:30:00.000000Z",
    "updated_at": "2026-01-15T10:30:00.000000Z"
  }
}

Update Managed Team ​

PATCH /reseller/managed-teams/{id}

Request Body:

FieldTypeRequiredDescription
namestringnoTeam display name
labelstringnoInternal label
planstringnoPlan tier: free, hobby, starter, or pro
trial_daysintegernoSet a trial period (1-365 days from now). Use 0 to clear an active trial.

Example:

bash
curl -X PATCH https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-... \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"label": "Updated Label", "plan": "pro", "trial_days": 30}'

Response (200):

json
{
  "data": {
    "id": "mt-uuid-...",
    "tenant_id": "tenant-uuid-...",
    "name": "Acme Corp",
    "slug": "acme-corp",
    "plan": "pro",
    "label": "Updated Label",
    "monitors_count": 12,
    "monitors_down_count": 1,
    "monitors_up_count": 10,
    "is_paused": false,
    "paused_at": null,
    "paused_reason": null,
    "on_trial": false,
    "trial_ends_at": null,
    "deleted_at": null,
    "days_until_purge": null,
    "created_at": "2026-01-15T10:30:00.000000Z",
    "updated_at": "2026-01-15T10:30:00.000000Z"
  }
}

Delete Managed Team ​

Soft-deletes the managed team and its underlying tenant. The team enters a 30-day grace period during which it can be restored. After the grace period, the team and all its data are permanently purged.

DELETE /reseller/managed-teams/{id}

Request Body:

FieldTypeRequiredDescription
confirm_namestringyesMust match the team name exactly to confirm deletion

Example:

bash
curl -X DELETE https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-... \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"confirm_name": "Acme Corp"}'

Response (204): No content.

This dispatches a team.deleted webhook event. The team can be restored within 30 days using the restore endpoint. After the grace period, all data (monitors, incidents, etc.) is permanently removed.

Pause Managed Team ​

Temporarily pause all monitoring for a managed team. Requires teams:write scope.

POST /reseller/managed-teams/{id}/pause

Request Body:

FieldTypeRequiredDescription
reasonstringnoReason for pausing (max 50 chars)

Example:

bash
curl -X POST https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../pause \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason": "maintenance"}'

Response (200):

json
{
  "data": {
    "message": "Team has been paused.",
    "is_paused": true,
    "paused_reason": "maintenance"
  }
}

Unpause Managed Team ​

Resume monitoring for a paused managed team. Requires teams:write scope.

POST /reseller/managed-teams/{id}/unpause

Example:

bash
curl -X POST https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../unpause \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "data": {
    "message": "Team has been unpaused.",
    "is_paused": false
  }
}

List Trashed Teams ​

List soft-deleted managed teams that are within their 30-day grace period. Requires teams:read scope.

GET /reseller/managed-teams/trashed

Query Parameters:

ParameterTypeDefaultDescription
pageinteger1Page number
per_pageinteger15Items per page

Example:

bash
curl "https://app.hoppla.dev/api/v1/reseller/managed-teams/trashed" \
  -H "Authorization: Bearer $TOKEN"

Response (200):

Paginated list of soft-deleted teams. Each team includes deleted_at and days_until_purge fields indicating when the team was deleted and how many days remain before permanent removal.

json
{
  "data": [
    {
      "id": "mt-uuid-...",
      "tenant_id": "tenant-uuid-...",
      "name": "Old Corp",
      "slug": "old-corp",
      "plan": "starter",
      "label": "Decommissioned",
      "monitors_count": 5,
      "monitors_down_count": 0,
      "monitors_up_count": 0,
      "is_paused": false,
      "paused_at": null,
      "paused_reason": null,
      "on_trial": false,
      "trial_ends_at": null,
      "deleted_at": "2026-03-10T12:00:00.000000Z",
      "days_until_purge": 25,
      "created_at": "2026-01-01T10:00:00.000000Z",
      "updated_at": "2026-03-10T12:00:00.000000Z"
    }
  ],
  "links": { "first": "...", "last": "...", "prev": null, "next": null },
  "meta": { "current_page": 1, "last_page": 1, "per_page": 15, "total": 1 }
}

Restore Managed Team ​

Restore a soft-deleted managed team. Must be within the 30-day grace period. Requires teams:write scope.

POST /reseller/managed-teams/{id}/restore

Example:

bash
curl -X POST https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../restore \
  -H "Authorization: Bearer $TOKEN"

Response (200):

Returns the restored team as a standard ManagedTeamResource with deleted_at set to null.

json
{
  "data": {
    "id": "mt-uuid-...",
    "tenant_id": "tenant-uuid-...",
    "name": "Old Corp",
    "slug": "old-corp",
    "plan": "starter",
    "label": "Decommissioned",
    "monitors_count": 5,
    "monitors_down_count": 0,
    "monitors_up_count": 0,
    "is_paused": false,
    "paused_at": null,
    "paused_reason": null,
    "on_trial": false,
    "trial_ends_at": null,
    "deleted_at": null,
    "days_until_purge": null,
    "created_at": "2026-01-01T10:00:00.000000Z",
    "updated_at": "2026-03-15T08:00:00.000000Z"
  }
}

Get Team Summary ​

Returns an overview of the managed team's monitoring health.

GET /reseller/managed-teams/{id}/summary

Example:

bash
curl https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../summary \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "data": {
    "monitors_count": 12,
    "monitors_up": 10,
    "monitors_down": 1,
    "active_incidents": 1,
    "notification_channels_count": 3,
    "status_pages_count": 2,
    "members_count": 5
  }
}

Get Team Usage ​

Returns usage metrics for billing purposes. Accepts an optional period query parameter to retrieve usage for a specific month.

GET /reseller/managed-teams/{id}/usage

Query Parameters:

ParameterTypeDefaultDescription
periodstringcurrent monthMonth in YYYY-MM format (e.g. 2026-03)

Example:

bash
curl "https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../usage?period=2026-03" \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "data": {
    "tenant_id": "tenant-uuid-...",
    "period": "2026-03",
    "monitors_count": 12,
    "checks_count": 43200,
    "api_requests_count": 1500,
    "notifications_sent_count": 42,
    "webhook_deliveries_count": 30
  }
}

Reseller API Tokens ​

Create scoped tokens specifically for reseller operations. These tokens have granular permissions and can only access reseller management endpoints.

Available Scopes ​

Most domains expose a read/write split plus a :manage variant that grants both :read and :write for that domain.

ScopeDescription
teams:readList and view managed teams, summaries, usage, feature overrides, templates, provisioning profiles, and cross-tenant overview/reports
teams:writeCreate, update, delete, pause, unpause, restore managed teams; manage templates, provisioning profiles, batch operations, and impersonation tokens
members:readList managed team members and invitations
members:writeCreate, update, delete team members; manage invitations; reset MFA
members:manageFull member management (grants both members:read and members:write)
branding:readView managed team branding configuration
branding:writeUpdate managed team branding configuration
branding:manageFull branding management (grants both branding:read and branding:write)
sso:readView Partner SSO configuration
sso:writeUpdate Partner SSO configuration, rotate secrets, generate SSO links
sso:manageFull SSO management (grants both sso:read and sso:write)
portal:readView portal settings, custom domains, and email suppression
portal:writeUpdate portal settings, manage custom domains, update email suppression
portal:manageFull portal management (grants both portal:read and portal:write)
api-keys:readList API keys for managed teams
api-keys:writeCreate and revoke API keys for managed teams
api-keys:manageFull API key management (grants both api-keys:read and api-keys:write)
webhooks:readList reseller webhooks and view delivery logs
webhooks:writeCreate, update, and delete reseller webhooks
webhooks:manageFull webhook management (grants both webhooks:read and webhooks:write)
notifications:readView a managed team's notification channels and rules
notifications:writeCreate, update, and delete a managed team's notification channels and rules
notifications:manageFull notification management (grants both notifications:read and notifications:write)
maintenance:readView a managed team's maintenance windows
maintenance:writeCreate, update, and delete a managed team's maintenance windows
maintenance:manageFull maintenance management (grants both maintenance:read and maintenance:write)
status-pages:readView a managed team's status pages
status-pages:writeCreate, update, and delete a managed team's status pages
status-pages:manageFull status-page management (grants both status-pages:read and status-pages:write)
monitors:readView a managed team's monitors, checks, and time-series data
monitors:writeCreate, update, delete, enable/disable, request runs for, and snooze a managed team's monitors and checks
monitors:manageFull monitor management (grants both monitors:read and monitors:write)
incidents:readView a managed team's incidents
incidents:writeUpdate and resolve a managed team's incidents
incidents:manageFull incident management (grants both incidents:read and incidents:write)

There are 12 scope domains in total: teams, members, branding, sso, portal, api-keys, webhooks, notifications, maintenance, status-pages, monitors, and incidents. Each domain offers :read, :write, and a :manage variant (which grants both).

List Available Scopes ​

GET /reseller/tokens/scopes

Response (200):

json
{
  "data": [
    { "value": "teams:read", "description": "List and view managed teams, summaries, usage, and feature overrides" },
    { "value": "teams:write", "description": "Create, update, delete, pause, unpause, and restore managed teams" }
  ]
}

List Reseller Tokens ​

GET /reseller/tokens

Response (200):

json
{
  "data": [
    {
      "id": "token-uuid-...",
      "name": "CI/CD Pipeline",
      "scopes": ["teams:read", "teams:write"],
      "purpose": "reseller",
      "is_active": true,
      "last_used_at": "2026-03-10T14:00:00.000000Z",
      "expires_at": null,
      "created_at": "2026-03-01T10:00:00.000000Z"
    }
  ]
}

Show Reseller Token ​

Retrieve a single reseller token by ID. Requires teams:read scope.

GET /reseller/tokens/{id}

Example:

bash
curl https://app.hoppla.dev/api/v1/reseller/tokens/token-uuid-... \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "data": {
    "id": "token-uuid-...",
    "name": "CI/CD Pipeline",
    "scopes": ["teams:read", "teams:write"],
    "purpose": "reseller",
    "is_active": true,
    "last_used_at": "2026-03-10T14:00:00.000000Z",
    "expires_at": null,
    "created_at": "2026-03-01T10:00:00.000000Z"
  }
}

Create Reseller Token ​

POST /reseller/tokens

Request Body:

FieldTypeRequiredDescription
namestringyesToken name (max 255 chars)
scopesstring[]yesArray of scope values (min 1)
expires_atdatetimenoExpiration date (ISO 8601). Omit for no expiry

Example:

bash
curl -X POST https://app.hoppla.dev/api/v1/reseller/tokens \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Provisioning Script",
    "scopes": ["teams:read", "teams:write", "members:manage"],
    "expires_at": "2026-12-31T23:59:59Z"
  }'

Response (201):

json
{
  "data": {
    "id": "token-uuid-...",
    "name": "Provisioning Script",
    "scopes": ["teams:read", "teams:write", "members:manage"],
    "purpose": "reseller",
    "is_active": true,
    "last_used_at": null,
    "expires_at": "2026-12-31T23:59:59.000000Z",
    "created_at": "2026-03-10T10:00:00.000000Z"
  },
  "plain_text_token": "rk-secretResellerTokenValue123..."
}

Important: The plain_text_token is only returned once. Store it securely. Reseller management tokens start with rk-; afterwards the list only shows a masked stub (e.g. rk-...aB3d).

Rotate Reseller Token ​

Create a replacement token with the same name and scopes. The old token gets a grace period (default 24 hours) during which both tokens work, enabling zero-downtime deployments.

POST /reseller/tokens/{id}/rotate

Request Body:

FieldTypeRequiredDescription
grace_period_hoursintegernoHow long the old token stays valid (1-168h, default: 24)

Example:

bash
curl -X POST https://app.hoppla.dev/api/v1/reseller/tokens/token-uuid-.../rotate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"grace_period_hours": 4}'

Response (201):

json
{
  "data": {
    "id": "new-token-uuid-...",
    "name": "CI Pipeline",
    "scopes": ["teams:read", "teams:write"],
    "purpose": "reseller",
    "is_active": true,
    "last_used_at": null,
    "expires_at": null,
    "created_at": "2026-03-10T14:00:00.000000Z"
  },
  "plain_text_token": "rk-newSecretResellerTokenValue...",
  "rotated_from": {
    "id": "old-token-uuid-...",
    "expires_at": "2026-03-11T14:00:00.000000Z",
    "grace_period_hours": 24
  }
}

Rotation workflow: (1) Call rotate, (2) deploy the new plain_text_token to your services, (3) once confirmed working, revoke the old token. Both tokens work during the grace period.

Enable/Disable Reseller Token ​

Toggle a token's active state without deleting it. Disabled tokens are immediately rejected during authentication but can be re-enabled at any time.

POST /reseller/tokens/{id}/toggle

Response (200):

json
{
  "data": {
    "id": "token-uuid-...",
    "name": "CI Pipeline",
    "scopes": ["teams:read", "teams:write"],
    "purpose": "reseller",
    "is_active": false,
    "last_used_at": "2026-03-10T12:00:00.000000Z",
    "expires_at": null,
    "created_at": "2026-03-01T10:00:00.000000Z"
  }
}

Use case: Temporarily disable a token during maintenance or incident response without losing the token configuration. Toggle again to re-enable.

Delete Reseller Token ​

Permanently removes the token. Use toggle (above) if you want to temporarily disable instead.

DELETE /reseller/tokens/{id}

Response (204): No content.

Token Requirements for Reseller Endpoints ​

Reseller management endpoints have two access requirements that must both be satisfied:

  1. Your tenant must be a reseller account.
  2. You must authenticate with a reseller-scoped token (rk-…) carrying the appropriate scope for the endpoint.

A general-purpose read (or write) token does not grant access to reseller management endpoints — even read-only ones. Create a reseller management token with the scopes you need (see Create Reseller Token).

Token TypeReseller Endpoint Access
General token (any scope)No access to reseller management endpoints
Reseller token with teams:readList/view managed teams
Reseller token with teams:writeCreate/update/delete managed teams
Reseller token with monitors:readView a managed team's monitors and checks
Reseller token with monitors:writeManage a managed team's monitors and checks

Impersonation Tokens ​

Create short-lived tokens to operate within a managed team's tenant context. These tokens are for automated cross-tenant operations and are logged to the audit trail.

List Active Impersonation Tokens ​

GET /reseller/managed-teams/{id}/impersonation-tokens

Returns only non-expired tokens.

Response (200):

json
{
  "data": [
    {
      "id": "token-uuid-...",
      "name": "Impersonation: Acme Corp",
      "scopes": ["read", "write"],
      "expires_at": "2026-03-10T14:15:00.000000Z",
      "impersonated_by_user_id": "reseller-user-uuid-...",
      "created_at": "2026-03-10T14:00:00.000000Z"
    }
  ]
}

Create Impersonation Token ​

POST /reseller/managed-teams/{id}/impersonation-tokens

Request Body:

FieldTypeRequiredDescription
ttl_minutesintegernoToken lifetime in minutes (1-15, default: 15)
scopesstring[]noArray of "read" and/or "write" (default: both)
labelstringnoDescriptive label for audit purposes (max 255 chars)

Example:

bash
curl -X POST https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../impersonation-tokens \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ttl_minutes": 10, "scopes": ["read"], "label": "Sync monitors"}'

Response (201):

json
{
  "data": {
    "id": "token-uuid-...",
    "managed_team_id": "mt-uuid-...",
    "managed_tenant_id": "tenant-uuid-...",
    "scopes": ["read"],
    "expires_at": "2026-03-10T14:10:00.000000Z",
    "ttl_minutes": 10
  },
  "plain_text_token": "ik-9xKsecretImpersonationToken..."
}

Important: The token is scoped to the managed team's tenant. Use it as a regular Bearer token — no X-Tenant-Id header needed. Impersonation tokens start with ik- and are shown only once.

Limits:

  • Maximum TTL: 15 minutes
  • Maximum 5 active (non-expired) impersonation tokens per managed team
  • All operations are logged with the originating reseller's identity

Revoke Impersonation Token ​

DELETE /reseller/managed-teams/{id}/impersonation-tokens/{tokenId}

Response (204): No content.

Using Impersonation Tokens ​

Once created, use the token as a standard Bearer token:

bash
curl https://app.hoppla.dev/api/v1/monitors \
  -H "Authorization: Bearer ik-9xKsecretImpersonationToken..."

The token automatically operates within the managed team's tenant context.


Partner SSO ​

Manage Partner SSO configuration for managed teams and generate one-click login links. Requires the sso:manage scope.

Get Partner SSO Config ​

GET /reseller/managed-teams/{id}/partner-sso

Response (200):

json
{
  "data": {
    "enabled": true,
    "has_shared_secret": true,
    "hide_password_login": false,
    "auto_register": true
  }
}

Update Partner SSO Config ​

PATCH /reseller/managed-teams/{id}/partner-sso

Request Body: All fields are optional.

FieldTypeDescription
enabledbooleanEnable/disable Partner SSO authentication
hide_password_loginbooleanHide the email/password form on the portal login page. When enabled, password-based login is also blocked server-side — direct API calls to /auth/login and /auth/token return a validation error for users in this tenant.
auto_registerbooleanAutomatically create accounts for new users signing in via SSO
auto_link_existing_usersbooleanLink someone who already has a password account in the team on their first SSO sign-in. Off by default: the sign-in of an existing member is refused until you turn it on. Turn it on only if your platform verifies email addresses

Response (200): Same structure as GET.

Rotate Shared Secret ​

Generate or rotate the shared secret used to sign JWTs. The plaintext secret is returned once.

Side effect: Rotating the shared secret automatically resets hide_password_login to false. This is a safety measure to prevent lockouts if the new secret has not yet been deployed. Re-enable hide_password_login via the update endpoint after confirming SSO works with the new secret.

POST /reseller/managed-teams/{id}/partner-sso/rotate-secret

Response (200):

json
{
  "data": {
    "enabled": true,
    "has_shared_secret": true
  },
  "shared_secret": "a1b2c3d4e5f6..."
}

Important: Copy the secret immediately — it cannot be retrieved again. Use it to sign HS256 JWTs on your platform.

Generate a one-click login URL for a managed team user. This is the simplest way to provide SSO — no JWT signing required on your side.

POST /reseller/managed-teams/{id}/sso-link

Request Body:

FieldTypeRequiredDescription
user_emailemailyesEmail of the user to log in
user_namestringnoName for auto-registration (if user doesn't exist yet)
redirect_tostringnoPath to redirect to after login (e.g. /dashboard/monitors)

Example:

bash
curl -X POST https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../sso-link \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"user_email": "[email protected]", "user_name": "Alice"}'

Response (200):

json
{
  "data": {
    "sso_url": "https://monitoring.acme.com/sso/partner?token=eyJ...&tenant=acme-corp",
    "expires_in_seconds": 300,
    "user_email": "[email protected]",
    "user_exists": true
  }
}

The link expires after 5 minutes and can only be used once (JWT replay prevention).

Portal-aware URLs: The SSO URL automatically resolves to the best available portal domain:

  1. If the managed team has a verified custom portal domain, it is used (e.g. https://monitoring.acme.com/sso/partner?token=...)
  2. Otherwise, if the reseller has a verified portal domain, that is used (e.g. https://portal.reseller.com/sso/partner?token=...)
  3. As a fallback, https://app.hoppla.dev is used

This ensures the session cookie is set on the correct domain, so users stay authenticated on the portal they normally use.

Auto-provisioning: If Partner SSO is not yet configured for the managed team, calling this endpoint automatically provisions it (generates a shared secret, enables SSO, enables auto-register and account linking). No manual setup required.

Account linking: If the user already has a password-based account, the SSO link automatically links it to Partner SSO on first use.


Feature Overrides ​

Override plan limits for individual managed teams. Allows resellers to grant features beyond (or below) a team's plan defaults.

Get Feature Overrides ​

GET /reseller/managed-teams/{id}/feature-overrides

Response (200):

json
{
  "data": {
    "plan": "free",
    "overrides": {
      "monitors": 100,
      "lighthouse_audits": true
    },
    "effective_limits": {
      "monitors": 100,
      "status_pages": 1,
      "lighthouse_audits": true,
      "..."
    }
  }
}

Update Feature Overrides ​

PUT /reseller/managed-teams/{id}/feature-overrides

Request Body:

json
{
  "overrides": {
    "monitors": 100,
    "lighthouse_audits": true,
    "status_pages": "__remove__"
  }
}

Override values by feature type:

FeatureTypeValues
monitorsnumber or nullNumber limit, or null for unlimited
status_pagesnumber or nullNumber limit, or null for unlimited
team_membersnumber or nullNumber limit, or null for unlimited
agent_limitnumberNumber of server agents
min_check_intervalnumberMinimum interval in seconds
data_retention_daysnumberDays of data retention
cron_heartbeatsbooleanEnable/disable
server_monitoringbooleanEnable/disable
lighthouse_auditsbooleanEnable/disable
geo_auditsbooleanEnable/disable
site_crawlsbooleanEnable/disable
web_vitalsbooleanEnable/disable
custom_domainsbooleanEnable/disable
on_call_schedulesbooleanEnable/disable
api_accessstring"read" or "full"
notification_channelsarrayAllowed channel types, e.g. ["email", "slack", "webhook"]
ai_incident_analysisbooleanEnable/disable AI incident analysis
ai_analysis_monthly_limitnumber or nullAI analyses per month (0–10,000), or null for unlimited
ai_report_monthly_limitnumberAI-written summaries of the monthly PDF report per month (0–100)
mobile_push_notificationsbooleanEnable/disable mobile push (only if your account is licensed for it)
white_label_brandingbooleanLet the team's own admins set its branding. You can brand your managed teams without it

Special values:

ValueMeaning
nullUnlimited (for numeric features)
"__remove__"Remove the override, reverting to plan default

How updates apply:

  • An update is partial: features you leave out keep their current override. Use "__remove__" to clear one.
  • A value never exceeds your own entitlement. Numbers are lowered to your limit, a feature you don't have is saved as off, channels are limited to yours, and null (unlimited) is kept only if you are unlimited yourself.
  • A value of the wrong type is refused with 422; a feature not in this table is ignored.
  • Provisioning profiles and portal registration defaults take the same features and follow the same rules, checked again when a team is created from them.

Response (200): Same structure as GET, with all of the team's overrides.


Team Members ​

Manage users within a managed team. Requires the members:manage scope.

List Team Members ​

GET /reseller/managed-teams/{id}/members

Response (200):

json
{
  "data": [
    {
      "id": "user-uuid-...",
      "name": "Alice Smith",
      "email": "[email protected]",
      "role": "owner",
      "language": "en",
      "mfa_enabled": true,
      "created_at": "2026-01-15T10:00:00.000000Z"
    }
  ]
}

Create Team Member ​

POST /reseller/managed-teams/{id}/members

Request Body:

FieldTypeRequiredDescription
emailemailyesUser's email address
namestringnoUser's display name (defaults to email if omitted)
passwordstringyesInitial password (min 12 chars, checked against breach databases)
rolestringnomember (default) or admin
languagestringnoen (default) or de

This creates a new account. If the email already belongs to an account, the request is rejected with 422 (An account with this email already exists. Invite it to the team instead.). An existing person keeps their own password and two-factor setup, so add them with an invitation instead.

Update Team Member ​

Update an existing team member's profile. Requires members:manage scope.

PATCH /reseller/managed-teams/{id}/members/{userId}

Request Body:

FieldTypeRequiredDescription
namestringnoUser's display name (max 255 chars)
languagestringnoen or de

Example:

bash
curl -X PATCH https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../members/user-uuid-... \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice Johnson", "language": "de"}'

Response (200):

json
{
  "data": {
    "id": "user-uuid-...",
    "name": "Alice Johnson",
    "email": "[email protected]",
    "role": "owner",
    "language": "de",
    "mfa_enabled": true,
    "created_at": "2026-01-15T10:00:00.000000Z"
  }
}

Remove Team Member ​

DELETE /reseller/managed-teams/{id}/members/{userId}

Response (204): No content.

Erase Team Member (GDPR Art. 17) ​

Erase a member's personal data on their behalf. This is not the same as removing them.

POST /reseller/managed-teams/{id}/members/{userId}/erase

Removal takes someone out of one team and they can be re-invited. Erasure destroys the person's identity across the whole platform — login, passkeys, device tokens, and every other team they belong to. It cannot be undone.

You can only erase a person whose teams are all yours (your own team and teams you manage). If they also belong to a team you do not manage, the request is rejected with 422. Remove them from your teams instead; only they can erase their whole account, through self-service.

There is no confirmation e-mail to the member. For a managed team you are the controller and Hoppla is your Art. 28 processor, so the instruction is yours to give; requiring them to click our link would defeat acting on their behalf. Instead the acting admin is recorded on the erasure request, an entry is written to the team's audit log, the member is e-mailed that it happened, and member.erased fires when it completes.

Scope: members:write.

Response (202):

json
{
  "message": "Erasure scheduled. The member will be notified, and member.erased will fire when it completes.",
  "erasure_id": "01a0c7...",
  "status": "confirmed"
}

Response (422) — the member is the sole admin of a team that still has other members. Promote another admin first; the block is about not stranding a team, and applies whoever owns it.

json
{
  "message": "This member belongs to a team that must be resolved first.",
  "blocking_teams": [{ "tenant_id": "01a0c7...", "tenant_name": "Acme", "reason": "sole_admin" }]
}

A managed team is never deleted by an erasure, even when the erased person was its last member.

Reset Team Member MFA ​

Remove MFA configuration for a team member (useful for account recovery).

POST /reseller/managed-teams/{id}/members/{userId}/reset-mfa

Two-factor settings belong to the person, not to one team, so a reset applies everywhere they sign in. It is therefore rejected with 422 for a person who also belongs to a team you do not manage. The breach response endpoint still blocks such a member in your team and revokes their sessions and tokens there, but leaves their two-factor settings alone.

Example:

bash
curl -X POST https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../members/user-uuid-.../reset-mfa \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "message": "MFA has been reset for this user."
}

List Team Invitations ​

GET /reseller/managed-teams/{id}/invitations

Response (200):

json
{
  "data": [
    {
      "id": "invitation-uuid-...",
      "email": "[email protected]",
      "role": "member",
      "is_pending": true,
      "is_expired": false,
      "expires_at": "2026-03-20T10:00:00.000000Z",
      "accepted_at": null,
      "created_at": "2026-03-13T10:00:00.000000Z"
    }
  ]
}

Create Team Invitation ​

POST /reseller/managed-teams/{id}/invitations

Request Body:

FieldTypeRequiredDescription
emailemailyesEmail to invite
rolestringnomember (default) or admin

Response (201):

json
{
  "data": {
    "id": "invitation-uuid-...",
    "email": "[email protected]",
    "role": "member",
    "is_pending": true,
    "is_expired": false,
    "expires_at": "2026-03-20T10:00:00.000000Z",
    "accepted_at": null,
    "created_at": "2026-03-13T10:00:00.000000Z"
  }
}

Resend Team Invitation ​

Resend the invitation email with a fresh token and extended expiry. Requires members:manage scope.

POST /reseller/managed-teams/{id}/invitations/{invitationId}/resend

Example:

bash
curl -X POST https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../invitations/invitation-uuid-.../resend \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "data": {
    "id": "invitation-uuid-...",
    "email": "[email protected]",
    "role": "member",
    "is_pending": true,
    "is_expired": false,
    "expires_at": "2026-03-22T10:00:00.000000Z",
    "accepted_at": null,
    "created_at": "2026-03-13T10:00:00.000000Z"
  }
}

Revoke Team Invitation ​

DELETE /reseller/managed-teams/{id}/invitations/{invitationId}

Team Monthly Reports ​

Manage monthly report settings for a managed team. Requires the teams:read / teams:write scope.

Monthly reports are enabled by default for all tenants. Resellers can disable them or configure custom recipients per managed team.

Get Monthly Report Config ​

GET /reseller/managed-teams/{managedTeam}/monthly-report

Response (200):

json
{
  "data": {
    "enabled": true,
    "recipients": ["[email protected]"],
    "last_sent_at": "2026-04-01T06:00:00Z",
    "ai_summary": false,
    "ai_summary_available": true
  }
}

ai_summary_available is true when the team's plan (or your override of ai_report_monthly_limit) includes AI summaries.

Update Monthly Report Config ​

PATCH /reseller/managed-teams/{managedTeam}/monthly-report

Request Body:

FieldTypeRequiredDescription
enabledbooleanyesEnable or disable monthly reports
recipientsarrayyesEmail addresses to receive the report (max 10). Empty array = sent to team admin.
ai_summarybooleannoHave the summary texts of the PDF report written with AI assistance. Left out, the stored value stays. true is refused with 422 when the team's plan does not include it.

Example:

bash
curl -X PATCH "https://app.hoppla.dev/api/v1/reseller/managed-teams/{id}/monthly-report" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true, "recipients": ["[email protected]", "[email protected]"]}'

Download the Monthly Report PDF ​

GET /reseller/managed-teams/{managedTeam}/reports/monthly/pdf?month=YYYY-MM

Requires the monitors:read scope. Returns application/pdf: the team's monthly report in your branding, with nothing of Hoppla in it. month defaults to the previous month; the current month gives the report so far, and the last 13 months are available. 503 with a Retry-After header means the PDF cannot be created right now. The same PDF is attached to the monthly email.


Team API Keys ​

Manage API keys for a managed team. Requires the api-keys:manage scope.

List Team API Keys ​

GET /reseller/managed-teams/{id}/api-keys

Response (200):

json
{
  "data": [
    {
      "id": "key-uuid-...",
      "name": "CI Integration",
      "scopes": ["read", "write"],
      "last_used_at": "2026-03-10T14:00:00.000000Z",
      "expires_at": "2026-12-31T23:59:59.000000Z",
      "created_at": "2026-03-01T10:00:00.000000Z"
    }
  ]
}

Create Team API Key ​

POST /reseller/managed-teams/{id}/api-keys

Request Body:

FieldTypeRequiredDescription
namestringyesKey name
scopesarraynoThe scopes of a customer API key: granular <area>:read, :write or :manage (for example ["monitors:read", "incidents:manage"], see Scopes), or the legacy ["read"] / ["read", "write"]. Default ["read", "write"]. If the team's plan allows read-only API access, only the read scopes are kept
expires_atdatetimenoExpiration date (ISO 8601, must be in the future). Omit for no expiry

Example:

bash
curl -X POST https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../api-keys \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "CI Integration",
    "scopes": ["read", "write"],
    "expires_at": "2026-12-31T23:59:59Z"
  }'

Response (201):

json
{
  "data": {
    "id": "key-uuid-...",
    "name": "CI Integration",
    "scopes": ["read", "write"],
    "last_used_at": null,
    "expires_at": "2026-12-31T23:59:59.000000Z",
    "created_at": "2026-03-10T10:00:00.000000Z"
  },
  "plain_text_token": "svc-secretServiceApiKeyValue..."
}

Important: The plain_text_token is only returned once. Store it securely. Managed-team service API keys start with svc- and grant access to that one team only.

Revoke Team API Key ​

DELETE /reseller/managed-teams/{id}/api-keys/{keyId}

Managed Team Branding ​

Manage branding settings for a specific managed team. Requires the branding:manage scope and the white_label_branding plan feature.

Get Managed Team Branding ​

GET /reseller/managed-teams/{id}/branding

Example:

bash
curl https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../branding \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "data": {
    "brand_name": "Acme Monitoring",
    "alert_prefix": "Acme Alert!",
    "logo_url": "https://example.com/logo.png",
    "logo_url_dark": "https://example.com/logo-dark.png",
    "symbol_url": "https://example.com/symbol.png",
    "primary_color": "#6366F1",
    "secondary_color": "#8B5CF6",
    "favicon_url": "https://example.com/favicon.ico",
    "footer_text": "Sent by Acme Monitoring",
    "footer_url": "https://acme-monitoring.com",
    "privacy_url": "https://acme-monitoring.com/privacy",
    "imprint_url": "https://acme-monitoring.com/imprint",
    "upgrade_url": "https://acme-monitoring.com/upgrade",
    "support_url": "https://acme-monitoring.com/support",
    "hide_powered_by": true,
    "notification_from_name": "Acme Alerts"
  }
}

Update Managed Team Branding ​

PATCH /reseller/managed-teams/{id}/branding

Request Body: All fields are optional. Only provided fields are updated. Same fields as the own-tenant branding endpoint (see Branding section), including upgrade_url and support_url.

Response (200): Same structure as GET.


Portal Custom Domain ​

Configure a custom domain for a managed team's portal. Requires the portal:manage scope.

Get Portal Domain ​

GET /reseller/managed-teams/{id}/portal-domain

Response (200):

json
{
  "data": {
    "domain": "monitoring.acme.com",
    "verified": true,
    "verified_at": "2026-03-05T12:00:00.000000Z",
    "cname_target": "cname.status.hoppla.dev"
  }
}

Set Portal Domain ​

PUT /reseller/managed-teams/{id}/portal-domain

Request Body:

FieldTypeRequiredDescription
domainstringyesCustom domain (e.g. monitoring.acme.com)

After setting, create a CNAME DNS record pointing the domain to cname.status.hoppla.dev, then verify.

Verify Portal Domain DNS ​

POST /reseller/managed-teams/{id}/portal-domain/verify

Checks if the CNAME record is correctly configured.

Remove Portal Domain ​

DELETE /reseller/managed-teams/{id}/portal-domain

Reseller Account Portal Domain ​

Configure a custom portal domain for the reseller's own account. Requires the portal:manage scope. This domain is used as the fallback portal URL for all managed teams that do not have their own custom domain.

Get Reseller Portal Domain ​

GET /reseller/portal-domain

Example:

bash
curl https://app.hoppla.dev/api/v1/reseller/portal-domain \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "data": {
    "domain": "portal.reseller.com",
    "verified": true,
    "verified_at": "2026-03-01T10:00:00.000000Z",
    "cname_target": "cname.status.hoppla.dev"
  }
}

Set Reseller Portal Domain ​

PUT /reseller/portal-domain

Request Body:

FieldTypeRequiredDescription
domainstringyesCustom domain (e.g. portal.reseller.com)

After setting, create a CNAME DNS record pointing the domain to cname.status.hoppla.dev, then verify.

Verify Reseller Portal Domain DNS ​

POST /reseller/portal-domain/verify

Checks if the CNAME record is correctly configured.

Remove Reseller Portal Domain ​

DELETE /reseller/portal-domain

Response (204): No content.


Portal Registration Settings ​

Configure self-service registration on your portal. When enabled, new users can sign up directly on your portal domain and are automatically provisioned as managed teams. Requires portal:manage scope.

Get Portal Settings ​

GET /reseller/portal-settings

Response (200):

json
{
  "data": {
    "portal_allow_registration": true,
    "portal_registration_defaults": {
      "plan": "starter",
      "trial_days": 14,
      "trial_plan": "pro",
      "feature_overrides": {
        "monitors": 25,
        "lighthouse_audits": true
      }
    }
  }
}

Update Portal Settings ​

PATCH /reseller/portal-settings

Request Body: All fields are optional.

FieldTypeDescription
portal_allow_registrationbooleanEnable/disable self-service registration
portal_registration_defaults.planstringDefault plan for new signups: free, hobby, starter, or pro
portal_registration_defaults.trial_daysinteger|nullTrial period (1-365 days). null to disable trial
portal_registration_defaults.trial_planstring|nullPlan during trial period. null to use default plan
portal_registration_defaults.feature_overridesobject|nullDefault feature overrides (same fields as Feature Overrides)

Example:

bash
curl -X PATCH https://app.hoppla.dev/api/v1/reseller/portal-settings \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "portal_allow_registration": true,
    "portal_registration_defaults": {
      "plan": "starter",
      "trial_days": 14,
      "trial_plan": "pro",
      "feature_overrides": {
        "monitors": 50,
        "lighthouse_audits": true,
        "geo_audits": true
      }
    }
  }'

Response (200): Same structure as GET.


Email Suppression ​

Control which platform emails are sent to your managed teams' users. When an email type is suppressed, Hoppla fires an email.* webhook instead, allowing you to send the email yourself. See Webhooks — Email Suppression for payload details.

Get Email Suppression Config ​

GET /reseller/email-suppression

Response (200):

json
{
  "data": {
    "suppressed_types": ["password_reset", "invitation"],
    "available_types": [
      { "key": "password_reset", "category": "auth", "description": "Password reset link" },
      { "key": "email_change_verify", "category": "auth", "description": "Email change verification link" },
      { "key": "email_changed_notify", "category": "auth", "description": "Email change notification to old address" },
      { "key": "invitation", "category": "team", "description": "Team member invitation" },
      { "key": "member_removed", "category": "team", "description": "Member removal notification" },
      { "key": "trial_ending", "category": "billing", "description": "Trial expiration reminder" },
      { "key": "trial_expired", "category": "billing", "description": "Trial expired notification" },
      { "key": "incident_created", "category": "monitoring", "description": "Incident alert email" },
      { "key": "incident_resolved", "category": "monitoring", "description": "Incident recovery email" },
      { "key": "warning", "category": "monitoring", "description": "Warning notification email" },
      { "key": "dns_changed", "category": "monitoring", "description": "DNS records changed notification" },
      { "key": "certificate_renewed", "category": "monitoring", "description": "Certificate renewed notification" },
      { "key": "digest", "category": "monitoring", "description": "Batched notification digest email" }
    ]
  }
}

Update Email Suppression Config ​

PUT /reseller/email-suppression

Request Body:

FieldTypeRequiredDescription
suppressed_typesstring[]yesArray of email type keys to suppress (see available types above)

Example:

bash
curl -X PUT https://app.hoppla.dev/api/v1/reseller/email-suppression \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "suppressed_types": [
      "password_reset",
      "invitation",
      "member_removed",
      "incident_created",
      "warning"
    ]
  }'

Response (200): Same structure as GET.

Important: Make sure you have a webhook notification channel subscribed to the corresponding email.* event types before enabling suppression. Otherwise suppressed emails will be silently dropped.


Cross-Tenant Overview & Queries ​

Aggregated views across all your managed tenants in a single call. No X-Tenant-Id header needed — these endpoints automatically span all teams you manage.

Overview ​

Get a health snapshot across all managed tenants.

GET /reseller/overview

Scope: teams:read

Response (200):

json
{
  "data": {
    "tenants": {
      "total": 487,
      "active": 450,
      "paused": 12,
      "trial": 25,
      "trial_expiring_7d": 8
    },
    "monitors": {
      "total": 4521,
      "up": 4380,
      "down": 23,
      "warning": 45,
      "paused": 73
    },
    "incidents": {
      "active": 18,
      "critical": 3,
      "major": 7,
      "minor": 8
    },
    "ssl_expiring_14d": 12
  }
}

List Monitors Across Tenants ​

GET /reseller/monitors

Scope: teams:read

ParameterTypeDescription
tenant_iduuidFilter to one managed team
statusstringup, down, warning, paused
is_enabledboolFilter by enabled state
searchstringSearch name or URL
tagstringFilter by tag
typestringMonitor type (http, ping, tcp)
sortstringname, status, created_at, url
sort_dirstringasc, desc
per_pageintMax 250

Response (200): Paginated list of monitors with tenant_name, status, uptime_30d, avg_response_ms, and checks array.

List Incidents Across Tenants ​

GET /reseller/incidents

Scope: teams:read

ParameterTypeDescription
tenant_iduuidFilter to one managed team
statusstringinvestigating, identified, monitoring, resolved
severitystringComma-separated: critical,major
acknowledgedboolFilter by acknowledgement
sincedatetimeOnly incidents after this time
sortstringstarted_at, severity, updated_at
per_pageintMax 250

Cross-Tenant Report ​

Monthly per-tenant summary of monitors, uptime, and incidents.

GET /reseller/report

Scope: teams:read

ParameterTypeDescription
periodstringYYYY-MM (default: current month)
tenant_idsstringComma-separated UUIDs to filter

Cross-Tenant DNS History ​

Zone-aware DNS history for a single monitor in a managed team — returns the zone apex, authoritative nameservers, confirmed record changes, and cross-resolver propagation without minting an impersonation token. The endpoint is scoped to a team you own and reads the managed tenant's DNS data directly.

GET /reseller/managed-teams/{managedTeam}/monitors/{monitorId}/dns-history

Scope: teams:read

Response (200):

json
{
  "data": {
    "monitor_id": "monitor-uuid-...",
    "records": [
      { "type": "A", "name": "acmecorp.com", "value": "203.0.113.10", "ttl": 300 }
    ],
    "zone": "acmecorp.com",
    "nameservers": ["ns1.acme-dns.com", "ns2.acme-dns.com"],
    "authoritative": true,
    "changes": [
      {
        "detected_at": "2026-01-15T16:00:00.000000Z",
        "changes": [
          { "type": "NS", "old": ["ns1.old-dns.com"], "new": ["ns1.acme-dns.com", "ns2.acme-dns.com"] }
        ]
      }
    ],
    "propagation": {
      "consistent": true,
      "resolvers": []
    }
  }
}

If the monitor has no DNS check, records, changes, and nameservers are empty and zone / authoritative / propagation are null.


Reseller Webhooks ​

Convenience endpoints for managing webhook subscriptions to reseller events. Creates both a notification channel and rule in a single call.

List Event Types ​

GET /reseller/webhook-event-types

Scope: webhooks:read

Returns all subscribable event types grouped by category.

Create Webhook ​

POST /reseller/webhooks

Scope: webhooks:write

Request:

json
{
  "name": "Ops Integration",
  "url": "https://hooks.example.com/hoppla",
  "secret": "whsec_...",
  "events": ["incident.created", "incident.resolved", "monitor.down", "ssl.expiring"],
  "tenant_filter": {
    "tenant_ids": ["uuid1", "uuid2"],
    "labels": ["prod-*"]
  }
}

The tenant_filter is optional — omit it to receive events from all managed tenants. Use tenant_ids for exact matches or labels with trailing wildcards for pattern matching.

List / Show / Update / Delete Webhooks ​

GET    /reseller/webhooks                  # Scope: webhooks:read
GET    /reseller/webhooks/{id}              # Scope: webhooks:read
PATCH  /reseller/webhooks/{id}              # Scope: webhooks:write
DELETE /reseller/webhooks/{id}              # Scope: webhooks:write

Delivery Logs ​

GET /reseller/webhooks/{id}/deliveries

Scope: webhooks:read

Returns the last 50 delivery attempts with status and error messages.


Batch Operations ​

High-throughput endpoints for managing monitors, checks, and incidents across managed teams.

Batch Create Monitors ​

Create up to 100 monitors in a single call.

POST /reseller/managed-teams/{managedTeam}/monitors/batch

Scope: teams:write

Request:

json
{
  "monitors": [
    {
      "name": "Customer Website",
      "url": "https://customer.com",
      "type": "http",
      "check_interval": 60,
      "tags": ["web", "production"],
      "checks": [
        { "type": "uptime" },
        { "type": "ssl" },
        { "type": "dns", "settings": { "record_types": ["A", "AAAA", "NS", "CAA"] } }
      ]
    }
  ]
}

If checks is omitted, default checks are created based on monitor type. Returns { created: [...], errors: [...] } with partial success support.

For dns checks, settings.record_types is an optional array constrained to A, AAAA, MX, CNAME, NS, TXT, SOA, SRV, CAA. It is validated identically to the customer-facing monitor write path, so a reseller token cannot persist record types the customer validator would reject.

Batch Update Monitors ​

PATCH /reseller/managed-teams/{managedTeam}/monitors/batch
json
{
  "updates": [
    { "id": "uuid1", "changes": { "check_interval": 120 } },
    { "id": "uuid2", "changes": { "is_enabled": false } }
  ]
}

Batch Delete Monitors ​

DELETE /reseller/managed-teams/{managedTeam}/monitors/batch
json
{ "monitor_ids": ["uuid1", "uuid2"] }

Batch Check Actions ​

Enable, disable, or snooze up to 200 checks in one call.

POST /reseller/managed-teams/{managedTeam}/checks/batch
json
{
  "actions": [
    { "check_id": "uuid1", "action": "disable" },
    { "check_id": "uuid2", "action": "snooze", "duration": "24h" }
  ]
}

Durations: 1h, 4h, 8h, 24h, 48h, 7d.

Batch Acknowledge Incidents ​

Acknowledge incidents across all managed tenants.

POST /reseller/incidents/batch-acknowledge
json
{ "incident_ids": ["uuid1", "uuid2"] }

Batch Stats Query ​

Get uptime and response time for up to 200 monitors in one call.

POST /monitors/stats-batch
json
{ "monitor_ids": ["uuid1", "uuid2"] }

Returns per-monitor uptime_30d and avg_response_ms.


Monitor Templates ​

Reusable monitor configurations with variable substitution.

CRUD ​

GET    /reseller/monitor-templates
POST   /reseller/monitor-templates
GET    /reseller/monitor-templates/{id}
PATCH  /reseller/monitor-templates/{id}
DELETE /reseller/monitor-templates/{id}

Create example:

json
{
  "name": "WordPress Standard",
  "monitors": [
    {
      "name_template": "{{domain}} Website",
      "url_template": "https://{{domain}}/",
      "type": "http",
      "check_interval": 60,
      "tags": ["{{plan}}"],
      "checks": [
        { "type": "uptime" },
        { "type": "ssl" },
        { "type": "dns", "settings": { "record_types": ["A", "AAAA", "NS", "CAA"] } },
        { "type": "security_headers" }
      ]
    }
  ],
  "variables": [
    { "name": "domain", "required": true, "description": "Customer domain" },
    { "name": "plan", "required": false, "default": "standard" }
  ]
}

As in batch creation, dns check settings.record_types is an optional array limited to A, AAAA, MX, CNAME, NS, TXT, SOA, SRV, CAA, validated identically to the customer write path.

Apply Template ​

POST /reseller/managed-teams/{managedTeam}/apply-template
json
{
  "template_id": "uuid",
  "variables": { "domain": "customer.com" }
}

Creates all monitors defined in the template with variables substituted.


Provisioning Profiles ​

Pre-configured tenant setup profiles for one-call provisioning.

GET    /reseller/provisioning-profiles
POST   /reseller/provisioning-profiles
GET    /reseller/provisioning-profiles/{id}
PATCH  /reseller/provisioning-profiles/{id}
DELETE /reseller/provisioning-profiles/{id}

Create example:

json
{
  "name": "WordPress Hosting Standard",
  "plan": "pro",
  "trial_days": 14,
  "feature_overrides": { "monitors": 50, "min_check_interval": 60 },
  "branding": { "brand_name": "CustomerCo Monitoring" },
  "monitor_template_id": "template-uuid"
}

Monitor Reports ​

Daily uptime breakdown for a monitor over a date range.

GET /monitors/{monitor}/report?from=2026-03-01&to=2026-04-01

Returns daily uptime_pct, avg_response_ms, total_checks, plus overall uptime_pct and incidents count. Max range: 90 days.


Enhanced Filtering ​

All list endpoints now support extended filtering and sorting.

Monitors (GET /monitors) ​

ParameterTypeDescription
is_enabledboolFilter by enabled state
statusstringup, down, warning, paused, maintenance
check_typestringOnly monitors with this check type
sortstringname, created_at, url, check_interval
sort_dirstringasc, desc
created_afterdateCreated after this date
created_beforedateCreated before this date

Incidents (GET /incidents) ​

ParameterTypeDescription
severitystringComma-separated: critical,major,minor
check_typestringFilter by originating check type
acknowledgedboolFilter by acknowledgement state
snoozedboolFilter by snooze state
sincedatetimeOnly incidents after this time
sortstringstarted_at, severity, updated_at, resolved_at

Managed Teams (GET /reseller/managed-teams) ​

ParameterTypeDescription
searchstringSearch by team name or label
planstringComma-separated plan filter
statusstringactive, paused, trial
has_incidentsboolOnly teams with active incidents
trial_expires_beforedateFilter by trial end date
sortstringname, created_at, monitors_count
per_pageintMax 250 (reseller endpoints)

Per-Team Management Endpoints ​

Beyond the X-Tenant-Id impersonation header, Hoppla offers dedicated reseller endpoints for managing a specific team's resources directly under /reseller/managed-teams/{id}/…. This is the recommended path for automation: it is scope-gated (each family maps to a scope domain such as monitors:write or notifications:manage) and every action is audited, without having to set a tenant context header.

The following per-team resource families are available under /reseller/managed-teams/{id}/:

ResourceEndpointsScope domain
Notification channelsGET/POST .../notification-channels, GET/PATCH/DELETE .../notification-channels/{cid}notifications
Notification rulesGET/POST .../notification-rules, GET/PATCH/DELETE .../notification-rules/{rid}notifications
Escalation policiesGET/POST .../escalation-policies, GET/PATCH/DELETE .../escalation-policies/{eid}notifications
On-call schedulesGET/POST .../on-call-schedules, GET/PATCH/DELETE .../on-call-schedules/{sid}notifications
Maintenance windowsGET/POST .../maintenance-windows, GET/PATCH/DELETE .../maintenance-windows/{mid}maintenance
Status pagesGET/POST .../status-pages, GET/PATCH/DELETE .../status-pages/{spid}status-pages
MonitorsGET/POST .../monitors, GET/PATCH/DELETE .../monitors/{mid}monitors
Monitor checks.../monitors/{mid}/checks plus enable, disable, request-run, snooze, and timeseries actionsmonitors
IncidentsGET .../incidents, update/resolve actionsincidents
Report (PDF)GET .../report.pdfteams / monitors

Each call requires a reseller management token (rk-…) carrying the matching scope. Use these endpoints rather than X-Tenant-Id impersonation when you want scope-limited, audited access to a single team's resources.

Cross-Tenant Operations ​

To perform operations within a managed team's context, add the X-Tenant-Id header to any standard API endpoint. See Impersonation for details. (For scope-limited, audited per-team automation, prefer the Per-Team Management Endpoints above.)

Example: List monitors in a managed team:

bash
curl https://app.hoppla.dev/api/v1/monitors \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Tenant-Id: managed-tenant-uuid-..."

Example: Create a monitor in a managed team:

bash
curl -X POST https://app.hoppla.dev/api/v1/monitors \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Tenant-Id: managed-tenant-uuid-..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Website",
    "url": "https://acmecorp.com",
    "type": "uptime",
    "interval": 60,
    "is_enabled": true
  }'

When X-Tenant-Id is set, all API responses are scoped to the target tenant. Data isolation is enforced at the infrastructure level.

Example: Snooze a check in a managed team:

bash
curl -X POST https://app.hoppla.dev/api/v1/checks/{check-id}/snooze \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Tenant-Id: managed-tenant-uuid-..." \
  -H "Content-Type: application/json" \
  -d '{"minutes": 60}'

Example: Unsnooze a check in a managed team:

bash
curl -X POST https://app.hoppla.dev/api/v1/checks/{check-id}/unsnooze \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Tenant-Id: managed-tenant-uuid-..."

Example: View audit log for a managed team:

bash
curl "https://app.hoppla.dev/api/v1/audit-log?action=check_snoozed,check_unsnoozed" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Tenant-Id: managed-tenant-uuid-..."

Uptime Badges ​

Uptime badges are public endpoints that require no authentication or X-Tenant-Id header — only the monitor UUID:

bash
# Default badge (shields.io style)
curl https://app.hoppla.dev/api/badges/{monitor-uuid}

# Small badge (dot + percentage)
curl https://app.hoppla.dev/api/badges/{monitor-uuid}/small

# Custom colors for dark mode embedding
curl "https://app.hoppla.dev/api/badges/{monitor-uuid}?label_color=2d333b&style=flat"

Badge URLs work on reseller portal custom domains too — the /api/badges/* path is served on your portal domain as well.


Notification Channels ​

Set up notification channels to receive webhook events. Channels are created in your own tenant context (no X-Tenant-Id).

Supported Channel Types ​

TypeConfig Fields
webhookurl (required), secret (optional, for HMAC signing)
emailrecipients (array of email addresses)
slackwebhook_url
discordwebhook_url
microsoft_teamswebhook_url
pagerdutyrouting_key
telegrambot_token, chat_id

Create Webhook Channel ​

bash
curl -X POST https://app.hoppla.dev/api/v1/notification-channels \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Provisioning Webhooks",
    "type": "webhook",
    "config": {
      "url": "https://your-server.com/hoppla-webhook",
      "secret": "your-hmac-secret"
    },
    "is_enabled": true
  }'

Create Notification Rule ​

Subscribe to reseller events:

bash
curl -X POST https://app.hoppla.dev/api/v1/notification-rules \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": "channel-uuid-...",
    "event_types": ["team.provisioned", "team.deleted", "monitor.created", "monitor.deleted"],
    "monitor_filter": {}
  }'

See Webhooks for payload examples and delivery details.


Branding ​

Customize how notifications and emails appear to your managed teams' users. Only available to tenants with the white_label_branding plan feature.

Get Branding ​

GET /branding

Example:

bash
curl https://app.hoppla.dev/api/v1/branding \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "data": {
    "brand_name": "Acme Monitoring",
    "alert_prefix": "Acme Alert!",
    "logo_url": "https://example.com/logo.png",
    "logo_url_dark": "https://example.com/logo-dark.png",
    "symbol_url": "https://example.com/symbol.png",
    "primary_color": "#6366F1",
    "secondary_color": "#8B5CF6",
    "favicon_url": "https://example.com/favicon.ico",
    "footer_text": "Sent by Acme Monitoring",
    "footer_url": "https://acme-monitoring.com",
    "privacy_url": "https://acme-monitoring.com/privacy",
    "imprint_url": "https://acme-monitoring.com/imprint",
    "upgrade_url": "https://acme-monitoring.com/upgrade",
    "support_url": "https://acme-monitoring.com/support",
    "hide_powered_by": true,
    "notification_from_name": "Acme Alerts"
  }
}

Update Branding ​

PATCH /branding

Request Body: All fields are optional. Only provided fields are updated.

FieldTypeDescription
brand_namestringBrand name shown in notifications and emails (max 100)
alert_prefixstringNotification prefix, e.g. "Acme Alert!" (max 50)
logo_urlurlLogo for light backgrounds (max 500, SSRF-validated)
logo_url_darkurlLogo for dark backgrounds / email headers (max 500, SSRF-validated)
symbol_urlurlSquare app symbol for the collapsed sidebar and the portal login, at least 64 × 64 px (max 500, SSRF-validated). Unset: the favicon, then the first letter of the brand name
primary_colorstringHex color code, e.g. #6366F1
secondary_colorstringHex color code for secondary elements
favicon_urlurlFavicon URL (max 500, SSRF-validated)
footer_textstringEmail footer text (max 200)
footer_urlurlEmail footer link URL (max 500, SSRF-validated)
privacy_urlurlPrivacy policy link in email footer (max 500, SSRF-validated)
imprint_urlurlImprint/legal link in email footer (max 500, SSRF-validated)
upgrade_urlurlUpgrade/upsell link URL (max 500, SSRF-validated)
support_urlurlSupport page link URL (max 500, SSRF-validated)
hide_powered_bybooleanRemove "Powered by Hoppla" attribution (requires white_label_branding plan feature)
notification_from_namestringCustom "From" name for notification emails (max 100)

Example:

bash
curl -X PATCH https://app.hoppla.dev/api/v1/branding \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "brand_name": "Acme Monitoring",
    "alert_prefix": "Acme Alert!",
    "primary_color": "#2563EB",
    "footer_text": "Sent by Acme Monitoring",
    "hide_powered_by": true
  }'

Response (200): Same structure as GET.

Note: The hide_powered_by field requires the white_label_branding plan feature. Setting it to true without this feature returns 403 Forbidden.

Managed teams: Use GET/PATCH /reseller/managed-teams/{id}/branding to manage branding for a specific managed team (requires branding:manage scope).


Custom SMTP Configuration ​

Send notification emails from your own domain. Requires the custom_smtp plan feature (reseller plan).

Get SMTP Config ​

GET /smtp-config

Example:

bash
curl https://app.hoppla.dev/api/v1/smtp-config \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "data": {
    "host": "smtp.example.com",
    "port": 587,
    "encryption": "tls",
    "username": "[email protected]",
    "password": "********",
    "from_address": "[email protected]",
    "from_name": "Acme Alerts"
  }
}

The password is always returned as ********. Sending ******** back in an update preserves the existing password.

Returns {"data": null} when no SMTP config is set (emails are sent via the platform default).

Update SMTP Config ​

PATCH /smtp-config

Request Body:

FieldTypeRequiredDescription
hoststringyesSMTP server hostname
portintegeryesPort: 25, 465, 587, or 2525
encryptionstringyestls or ssl
usernamestringnoSMTP username
passwordstringnoSMTP password (send ******** to keep existing)
from_addressemailnoSender email address
from_namestringnoSender display name

Example:

bash
curl -X PATCH https://app.hoppla.dev/api/v1/smtp-config \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "host": "smtp.example.com",
    "port": 587,
    "encryption": "tls",
    "username": "[email protected]",
    "password": "your-smtp-password",
    "from_address": "[email protected]",
    "from_name": "Acme Alerts"
  }'

Response (200): Same structure as GET.

Test SMTP Connection ​

POST /smtp-config/test

Rate limited to 5 requests per minute.

Example:

bash
curl -X POST https://app.hoppla.dev/api/v1/smtp-config/test \
  -H "Authorization: Bearer $TOKEN"

Response (200):

json
{
  "success": true
}

Remove SMTP Config ​

DELETE /smtp-config

Removes the custom SMTP configuration. Emails will be sent via the platform default.

Example:

bash
curl -X DELETE https://app.hoppla.dev/api/v1/smtp-config \
  -H "Authorization: Bearer $TOKEN"

Response (204): No content.

Admin only: SMTP configuration can only be managed by tenant admins. Regular users receive 403 Forbidden.