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/v1Authentication
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:
{
"message": "Human-readable error description",
"errors": {
"field_name": ["Validation error message"]
}
}| Status | Meaning |
|---|---|
| 401 | Missing or invalid authentication token |
| 403 | Insufficient permissions (not a reseller, or team not managed by you) |
| 404 | Resource not found |
| 422 | Validation failed |
| 429 | Rate limit exceeded |
Authentication Endpoints
Create Token
Obtain an API token by exchanging credentials.
POST /auth/tokenRate limit: throttle:auth (stricter than standard API limit)
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
email | string | yes | Account email address |
password | string | yes | Account password |
device_name | string | yes | Identifier for the token (e.g. "CI Server") |
Example:
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):
{
"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_teamsarray 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 withrk-, impersonation tokens withik-, and managed-team service API keys withsvc-. 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 /userExample:
curl https://app.hoppla.dev/api/v1/user \
-H "Authorization: Bearer $TOKEN"Response (200):
{
"id": "9a1b2c3d-e5f6-...",
"name": "Reseller Account",
"email": "[email protected]",
"tenant_id": "tenant-uuid-...",
"role": "admin",
"language": "en"
}Revoke Token
DELETE /auth/tokenExample:
curl -X DELETE https://app.hoppla.dev/api/v1/auth/token \
-H "Authorization: Bearer $TOKEN"Response (200):
{
"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-teamsQuery Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
per_page | integer | 15 | Items per page |
Example:
curl "https://app.hoppla.dev/api/v1/reseller/managed-teams?page=1" \
-H "Authorization: Bearer $TOKEN"Response (200):
{
"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-teamsRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Team display name (max 255 chars) |
slug | string | yes | URL-safe identifier (lowercase, hyphens only, max 100 chars, unique) |
plan | string | no | Plan tier: free, hobby, starter, or pro (default: free) |
label | string | no | Internal label for your reference (max 255 chars) |
admin_email | string | no | Email for the team's admin user |
admin_name | string | no | Name for the team's admin user |
admin_password | string | conditional | Required 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:
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):
{
"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.provisionedwebhook event.
Get Managed Team
GET /reseller/managed-teams/{id}Example:
curl https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-... \
-H "Authorization: Bearer $TOKEN"Response (200):
{
"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:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | no | Team display name |
label | string | no | Internal label |
plan | string | no | Plan tier: free, hobby, starter, or pro |
trial_days | integer | no | Set a trial period (1-365 days from now). Use 0 to clear an active trial. |
Example:
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):
{
"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:
| Field | Type | Required | Description |
|---|---|---|---|
confirm_name | string | yes | Must match the team name exactly to confirm deletion |
Example:
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.deletedwebhook 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}/pauseRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | no | Reason for pausing (max 50 chars) |
Example:
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):
{
"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}/unpauseExample:
curl -X POST https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../unpause \
-H "Authorization: Bearer $TOKEN"Response (200):
{
"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/trashedQuery Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
per_page | integer | 15 | Items per page |
Example:
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.
{
"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}/restoreExample:
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.
{
"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}/summaryExample:
curl https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../summary \
-H "Authorization: Bearer $TOKEN"Response (200):
{
"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}/usageQuery Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
period | string | current month | Month in YYYY-MM format (e.g. 2026-03) |
Example:
curl "https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../usage?period=2026-03" \
-H "Authorization: Bearer $TOKEN"Response (200):
{
"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.
| Scope | Description |
|---|---|
teams:read | List and view managed teams, summaries, usage, feature overrides, templates, provisioning profiles, and cross-tenant overview/reports |
teams:write | Create, update, delete, pause, unpause, restore managed teams; manage templates, provisioning profiles, batch operations, and impersonation tokens |
members:read | List managed team members and invitations |
members:write | Create, update, delete team members; manage invitations; reset MFA |
members:manage | Full member management (grants both members:read and members:write) |
branding:read | View managed team branding configuration |
branding:write | Update managed team branding configuration |
branding:manage | Full branding management (grants both branding:read and branding:write) |
sso:read | View Partner SSO configuration |
sso:write | Update Partner SSO configuration, rotate secrets, generate SSO links |
sso:manage | Full SSO management (grants both sso:read and sso:write) |
portal:read | View portal settings, custom domains, and email suppression |
portal:write | Update portal settings, manage custom domains, update email suppression |
portal:manage | Full portal management (grants both portal:read and portal:write) |
api-keys:read | List API keys for managed teams |
api-keys:write | Create and revoke API keys for managed teams |
api-keys:manage | Full API key management (grants both api-keys:read and api-keys:write) |
webhooks:read | List reseller webhooks and view delivery logs |
webhooks:write | Create, update, and delete reseller webhooks |
webhooks:manage | Full webhook management (grants both webhooks:read and webhooks:write) |
notifications:read | View a managed team's notification channels and rules |
notifications:write | Create, update, and delete a managed team's notification channels and rules |
notifications:manage | Full notification management (grants both notifications:read and notifications:write) |
maintenance:read | View a managed team's maintenance windows |
maintenance:write | Create, update, and delete a managed team's maintenance windows |
maintenance:manage | Full maintenance management (grants both maintenance:read and maintenance:write) |
status-pages:read | View a managed team's status pages |
status-pages:write | Create, update, and delete a managed team's status pages |
status-pages:manage | Full status-page management (grants both status-pages:read and status-pages:write) |
monitors:read | View a managed team's monitors, checks, and time-series data |
monitors:write | Create, update, delete, enable/disable, request runs for, and snooze a managed team's monitors and checks |
monitors:manage | Full monitor management (grants both monitors:read and monitors:write) |
incidents:read | View a managed team's incidents |
incidents:write | Update and resolve a managed team's incidents |
incidents:manage | Full 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/scopesResponse (200):
{
"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/tokensResponse (200):
{
"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:
curl https://app.hoppla.dev/api/v1/reseller/tokens/token-uuid-... \
-H "Authorization: Bearer $TOKEN"Response (200):
{
"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/tokensRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Token name (max 255 chars) |
scopes | string[] | yes | Array of scope values (min 1) |
expires_at | datetime | no | Expiration date (ISO 8601). Omit for no expiry |
Example:
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):
{
"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_tokenis only returned once. Store it securely. Reseller management tokens start withrk-; 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}/rotateRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
grace_period_hours | integer | no | How long the old token stays valid (1-168h, default: 24) |
Example:
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):
{
"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_tokento 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}/toggleResponse (200):
{
"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:
- Your tenant must be a reseller account.
- 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 Type | Reseller Endpoint Access |
|---|---|
| General token (any scope) | No access to reseller management endpoints |
Reseller token with teams:read | List/view managed teams |
Reseller token with teams:write | Create/update/delete managed teams |
Reseller token with monitors:read | View a managed team's monitors and checks |
Reseller token with monitors:write | Manage 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-tokensReturns only non-expired tokens.
Response (200):
{
"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-tokensRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
ttl_minutes | integer | no | Token lifetime in minutes (1-15, default: 15) |
scopes | string[] | no | Array of "read" and/or "write" (default: both) |
label | string | no | Descriptive label for audit purposes (max 255 chars) |
Example:
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):
{
"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-Idheader needed. Impersonation tokens start withik-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:
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-ssoResponse (200):
{
"data": {
"enabled": true,
"has_shared_secret": true,
"hide_password_login": false,
"auto_register": true
}
}Update Partner SSO Config
PATCH /reseller/managed-teams/{id}/partner-ssoRequest Body: All fields are optional.
| Field | Type | Description |
|---|---|---|
enabled | boolean | Enable/disable Partner SSO authentication |
hide_password_login | boolean | Hide 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_register | boolean | Automatically create accounts for new users signing in via SSO |
auto_link_existing_users | boolean | Link 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_logintofalse. This is a safety measure to prevent lockouts if the new secret has not yet been deployed. Re-enablehide_password_loginvia the update endpoint after confirming SSO works with the new secret.
POST /reseller/managed-teams/{id}/partner-sso/rotate-secretResponse (200):
{
"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 SSO Login Link
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-linkRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
user_email | yes | Email of the user to log in | |
user_name | string | no | Name for auto-registration (if user doesn't exist yet) |
redirect_to | string | no | Path to redirect to after login (e.g. /dashboard/monitors) |
Example:
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):
{
"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:
- If the managed team has a verified custom portal domain, it is used (e.g.
https://monitoring.acme.com/sso/partner?token=...) - Otherwise, if the reseller has a verified portal domain, that is used (e.g.
https://portal.reseller.com/sso/partner?token=...) - As a fallback,
https://app.hoppla.devis 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-overridesResponse (200):
{
"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-overridesRequest Body:
{
"overrides": {
"monitors": 100,
"lighthouse_audits": true,
"status_pages": "__remove__"
}
}Override values by feature type:
| Feature | Type | Values |
|---|---|---|
monitors | number or null | Number limit, or null for unlimited |
status_pages | number or null | Number limit, or null for unlimited |
team_members | number or null | Number limit, or null for unlimited |
agent_limit | number | Number of server agents |
min_check_interval | number | Minimum interval in seconds |
data_retention_days | number | Days of data retention |
cron_heartbeats | boolean | Enable/disable |
server_monitoring | boolean | Enable/disable |
lighthouse_audits | boolean | Enable/disable |
geo_audits | boolean | Enable/disable |
site_crawls | boolean | Enable/disable |
web_vitals | boolean | Enable/disable |
custom_domains | boolean | Enable/disable |
on_call_schedules | boolean | Enable/disable |
api_access | string | "read" or "full" |
notification_channels | array | Allowed channel types, e.g. ["email", "slack", "webhook"] |
ai_incident_analysis | boolean | Enable/disable AI incident analysis |
ai_analysis_monthly_limit | number or null | AI analyses per month (0–10,000), or null for unlimited |
ai_report_monthly_limit | number | AI-written summaries of the monthly PDF report per month (0–100) |
mobile_push_notifications | boolean | Enable/disable mobile push (only if your account is licensed for it) |
white_label_branding | boolean | Let the team's own admins set its branding. You can brand your managed teams without it |
Special values:
| Value | Meaning |
|---|---|
null | Unlimited (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}/membersResponse (200):
{
"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}/membersRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
email | yes | User's email address | |
name | string | no | User's display name (defaults to email if omitted) |
password | string | yes | Initial password (min 12 chars, checked against breach databases) |
role | string | no | member (default) or admin |
language | string | no | en (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:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | no | User's display name (max 255 chars) |
language | string | no | en or de |
Example:
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):
{
"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}/eraseRemoval 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):
{
"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.
{
"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-mfaTwo-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:
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):
{
"message": "MFA has been reset for this user."
}List Team Invitations
GET /reseller/managed-teams/{id}/invitationsResponse (200):
{
"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}/invitationsRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
email | yes | Email to invite | |
role | string | no | member (default) or admin |
Response (201):
{
"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}/resendExample:
curl -X POST https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../invitations/invitation-uuid-.../resend \
-H "Authorization: Bearer $TOKEN"Response (200):
{
"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-reportResponse (200):
{
"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-reportRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
enabled | boolean | yes | Enable or disable monthly reports |
recipients | array | yes | Email addresses to receive the report (max 10). Empty array = sent to team admin. |
ai_summary | boolean | no | Have 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:
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-MMRequires 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-keysResponse (200):
{
"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-keysRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Key name |
scopes | array | no | The 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_at | datetime | no | Expiration date (ISO 8601, must be in the future). Omit for no expiry |
Example:
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):
{
"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_tokenis only returned once. Store it securely. Managed-team service API keys start withsvc-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}/brandingExample:
curl https://app.hoppla.dev/api/v1/reseller/managed-teams/mt-uuid-.../branding \
-H "Authorization: Bearer $TOKEN"Response (200):
{
"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}/brandingRequest 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-domainResponse (200):
{
"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-domainRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
domain | string | yes | Custom 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/verifyChecks if the CNAME record is correctly configured.
Remove Portal Domain
DELETE /reseller/managed-teams/{id}/portal-domainReseller 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-domainExample:
curl https://app.hoppla.dev/api/v1/reseller/portal-domain \
-H "Authorization: Bearer $TOKEN"Response (200):
{
"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-domainRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
domain | string | yes | Custom 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/verifyChecks if the CNAME record is correctly configured.
Remove Reseller Portal Domain
DELETE /reseller/portal-domainResponse (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-settingsResponse (200):
{
"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-settingsRequest Body: All fields are optional.
| Field | Type | Description |
|---|---|---|
portal_allow_registration | boolean | Enable/disable self-service registration |
portal_registration_defaults.plan | string | Default plan for new signups: free, hobby, starter, or pro |
portal_registration_defaults.trial_days | integer|null | Trial period (1-365 days). null to disable trial |
portal_registration_defaults.trial_plan | string|null | Plan during trial period. null to use default plan |
portal_registration_defaults.feature_overrides | object|null | Default feature overrides (same fields as Feature Overrides) |
Example:
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-suppressionResponse (200):
{
"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-suppressionRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
suppressed_types | string[] | yes | Array of email type keys to suppress (see available types above) |
Example:
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/overviewScope: teams:read
Response (200):
{
"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/monitorsScope: teams:read
| Parameter | Type | Description |
|---|---|---|
tenant_id | uuid | Filter to one managed team |
status | string | up, down, warning, paused |
is_enabled | bool | Filter by enabled state |
search | string | Search name or URL |
tag | string | Filter by tag |
type | string | Monitor type (http, ping, tcp) |
sort | string | name, status, created_at, url |
sort_dir | string | asc, desc |
per_page | int | Max 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/incidentsScope: teams:read
| Parameter | Type | Description |
|---|---|---|
tenant_id | uuid | Filter to one managed team |
status | string | investigating, identified, monitoring, resolved |
severity | string | Comma-separated: critical,major |
acknowledged | bool | Filter by acknowledgement |
since | datetime | Only incidents after this time |
sort | string | started_at, severity, updated_at |
per_page | int | Max 250 |
Cross-Tenant Report
Monthly per-tenant summary of monitors, uptime, and incidents.
GET /reseller/reportScope: teams:read
| Parameter | Type | Description |
|---|---|---|
period | string | YYYY-MM (default: current month) |
tenant_ids | string | Comma-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-historyScope: teams:read
Response (200):
{
"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-typesScope: webhooks:read
Returns all subscribable event types grouped by category.
Create Webhook
POST /reseller/webhooksScope: webhooks:write
Request:
{
"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:writeDelivery Logs
GET /reseller/webhooks/{id}/deliveriesScope: 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/batchScope: teams:write
Request:
{
"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{
"updates": [
{ "id": "uuid1", "changes": { "check_interval": 120 } },
{ "id": "uuid2", "changes": { "is_enabled": false } }
]
}Batch Delete Monitors
DELETE /reseller/managed-teams/{managedTeam}/monitors/batch{ "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{
"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{ "incident_ids": ["uuid1", "uuid2"] }Batch Stats Query
Get uptime and response time for up to 200 monitors in one call.
POST /monitors/stats-batch{ "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:
{
"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{
"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:
{
"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-01Returns 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)
| Parameter | Type | Description |
|---|---|---|
is_enabled | bool | Filter by enabled state |
status | string | up, down, warning, paused, maintenance |
check_type | string | Only monitors with this check type |
sort | string | name, created_at, url, check_interval |
sort_dir | string | asc, desc |
created_after | date | Created after this date |
created_before | date | Created before this date |
Incidents (GET /incidents)
| Parameter | Type | Description |
|---|---|---|
severity | string | Comma-separated: critical,major,minor |
check_type | string | Filter by originating check type |
acknowledged | bool | Filter by acknowledgement state |
snoozed | bool | Filter by snooze state |
since | datetime | Only incidents after this time |
sort | string | started_at, severity, updated_at, resolved_at |
Managed Teams (GET /reseller/managed-teams)
| Parameter | Type | Description |
|---|---|---|
search | string | Search by team name or label |
plan | string | Comma-separated plan filter |
status | string | active, paused, trial |
has_incidents | bool | Only teams with active incidents |
trial_expires_before | date | Filter by trial end date |
sort | string | name, created_at, monitors_count |
per_page | int | Max 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}/:
| Resource | Endpoints | Scope domain |
|---|---|---|
| Notification channels | GET/POST .../notification-channels, GET/PATCH/DELETE .../notification-channels/{cid} | notifications |
| Notification rules | GET/POST .../notification-rules, GET/PATCH/DELETE .../notification-rules/{rid} | notifications |
| Escalation policies | GET/POST .../escalation-policies, GET/PATCH/DELETE .../escalation-policies/{eid} | notifications |
| On-call schedules | GET/POST .../on-call-schedules, GET/PATCH/DELETE .../on-call-schedules/{sid} | notifications |
| Maintenance windows | GET/POST .../maintenance-windows, GET/PATCH/DELETE .../maintenance-windows/{mid} | maintenance |
| Status pages | GET/POST .../status-pages, GET/PATCH/DELETE .../status-pages/{spid} | status-pages |
| Monitors | GET/POST .../monitors, GET/PATCH/DELETE .../monitors/{mid} | monitors |
| Monitor checks | .../monitors/{mid}/checks plus enable, disable, request-run, snooze, and timeseries actions | monitors |
| Incidents | GET .../incidents, update/resolve actions | incidents |
| Report (PDF) | GET .../report.pdf | teams / 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:
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:
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-Idis 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:
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:
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:
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:
# 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
| Type | Config Fields |
|---|---|
webhook | url (required), secret (optional, for HMAC signing) |
email | recipients (array of email addresses) |
slack | webhook_url |
discord | webhook_url |
microsoft_teams | webhook_url |
pagerduty | routing_key |
telegram | bot_token, chat_id |
Create Webhook Channel
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:
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 /brandingExample:
curl https://app.hoppla.dev/api/v1/branding \
-H "Authorization: Bearer $TOKEN"Response (200):
{
"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 /brandingRequest Body: All fields are optional. Only provided fields are updated.
| Field | Type | Description |
|---|---|---|
brand_name | string | Brand name shown in notifications and emails (max 100) |
alert_prefix | string | Notification prefix, e.g. "Acme Alert!" (max 50) |
logo_url | url | Logo for light backgrounds (max 500, SSRF-validated) |
logo_url_dark | url | Logo for dark backgrounds / email headers (max 500, SSRF-validated) |
symbol_url | url | Square 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_color | string | Hex color code, e.g. #6366F1 |
secondary_color | string | Hex color code for secondary elements |
favicon_url | url | Favicon URL (max 500, SSRF-validated) |
footer_text | string | Email footer text (max 200) |
footer_url | url | Email footer link URL (max 500, SSRF-validated) |
privacy_url | url | Privacy policy link in email footer (max 500, SSRF-validated) |
imprint_url | url | Imprint/legal link in email footer (max 500, SSRF-validated) |
upgrade_url | url | Upgrade/upsell link URL (max 500, SSRF-validated) |
support_url | url | Support page link URL (max 500, SSRF-validated) |
hide_powered_by | boolean | Remove "Powered by Hoppla" attribution (requires white_label_branding plan feature) |
notification_from_name | string | Custom "From" name for notification emails (max 100) |
Example:
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_byfield requires thewhite_label_brandingplan feature. Setting it totruewithout this feature returns403 Forbidden.
Managed teams: Use
GET/PATCH /reseller/managed-teams/{id}/brandingto manage branding for a specific managed team (requiresbranding:managescope).
Custom SMTP Configuration
Send notification emails from your own domain. Requires the custom_smtp plan feature (reseller plan).
Get SMTP Config
GET /smtp-configExample:
curl https://app.hoppla.dev/api/v1/smtp-config \
-H "Authorization: Bearer $TOKEN"Response (200):
{
"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-configRequest Body:
| Field | Type | Required | Description |
|---|---|---|---|
host | string | yes | SMTP server hostname |
port | integer | yes | Port: 25, 465, 587, or 2525 |
encryption | string | yes | tls or ssl |
username | string | no | SMTP username |
password | string | no | SMTP password (send ******** to keep existing) |
from_address | no | Sender email address | |
from_name | string | no | Sender display name |
Example:
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/testRate limited to 5 requests per minute.
Example:
curl -X POST https://app.hoppla.dev/api/v1/smtp-config/test \
-H "Authorization: Bearer $TOKEN"Response (200):
{
"success": true
}Remove SMTP Config
DELETE /smtp-configRemoves the custom SMTP configuration. Emails will be sent via the platform default.
Example:
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.