Admin Endpoints¶
All admin endpoints require both an IP allowlist match and a valid HMAC request signature — see Authentication → Admin Authentication. Not rate-limited by the standard tiers (protection here is the allowlist + signature instead).
Base Path: /api/v1/admin
User Management¶
Create User¶
Endpoint: POST /api/v1/admin/users
Request Body (UserCreateRequest):
Response 201 Created (UserCreateResponse, identical shape to UserResponse):
{
"user_hash": "3f2a1b9c-...",
"user_id": 12345,
"api_token": "generated-43-char-token",
"is_active": true,
"provider_hash": null
}
provider_hash is always null on creation — a freshly created user cannot yet own a provider.
Error Responses:
409:duplicate_name— a user with thisuser_idalready exists
Get User¶
Endpoint: GET /api/v1/admin/users/{user_id}
Response 200 OK (UserResponse): same shape as Create User's response. api_token is always included here too — this endpoint returns the user's current live token, not just at creation time. provider_hash is populated if this user owns a provider account (looked up by owner hash), otherwise null.
Error Responses:
404: User not found
List User's Connections¶
Despite the path resembling "providers owned by this user," this returns the providers this user is connected to as an end-user (i.e. the same relationship as User Self-Service → List My Connections, but looked up by an admin on the user's behalf) — not any provider account the user itself owns.
Endpoint: GET /api/v1/admin/users/{user_id}/providers
Response 200 OK (ConnectionsResponse): identical shape to User Self-Service → List My Connections.
Error Responses:
404: User not found
Get User's Specific Connection¶
Endpoint: GET /api/v1/admin/users/{user_id}/providers/{provider_name}
Response 200 OK (ConnectionResponse).
Error Responses:
404: User or provider not found
Delete User¶
Endpoint: DELETE /api/v1/admin/users/{user_id}
Response 204 No Content
Notes: Cascades to the user's subscriptions and connection records.
Error Responses:
404: User not found
Update User Status¶
Endpoint: PATCH /api/v1/admin/users/{user_id}/status
Request Body (UserStatusUpdateRequest):
Response 200 OK (UserResponse)
Error Responses:
404: User not found
Refresh User Token¶
Note the path — user_id goes in the request body, not the URL, unlike most other single-resource admin operations on this page.
Endpoint: POST /api/v1/admin/users/refresh-token
Request Body (TokenRefreshRequest):
Response 200 OK (TokenRefreshResponse):
Error Responses:
404: User not found
Provider Management¶
Create Provider¶
Endpoint: POST /api/v1/admin/providers
Request Body (ProviderCreateRequest):
{
"owner_hash": "3f2a1b9c-...",
"provider_name": "my-provider",
"provider_url": "https://t.me/examplebot"
}
| Field | Type | Required | Constraints |
|---|---|---|---|
owner_hash |
string | Yes | Must be an existing user's user_hash (UUID) |
provider_name |
string | Yes | 4-16 chars, pattern ^[a-z0-9]+(?:-[a-z0-9]+)*$ (lowercase, digits, single hyphens) |
provider_url |
string · null | No | Max 255 chars; validated as a safe external URL (SSRF protections apply) |
Response 201 Created (ProviderCreateResponse, identical shape to ProviderResponse):
{
"provider_hash": "9c8b7a6f-...",
"owner_hash": "3f2a1b9c-...",
"provider_name": "my-provider",
"api_token": "generated-43-char-token",
"provider_url": "https://t.me/examplebot",
"is_active": true
}
Error Responses:
422: Invalidprovider_namepattern/length, orprovider_urlrejected by URL validation (invalid_url)409: Provider name already taken
List All Providers¶
Endpoint: GET /api/v1/admin/providers
Response 200 OK (AllProvidersResponse):
Mapping of provider_name → provider_hash for every provider on the instance.
Get Provider by Name¶
Endpoint: GET /api/v1/admin/providers/name/{provider_name}
Response 200 OK (ProviderResponse): same shape as Create Provider's response, including the live api_token.
Error Responses:
404: Provider not found
Get Provider by Owner¶
Looks up the owning user first — if owner_id doesn't correspond to an existing user, this fails as a user lookup, not (yet) as a provider lookup.
Endpoint: GET /api/v1/admin/providers/owner/{owner_id}
Response 200 OK (ProviderResponse)
Error Responses:
404: User with thisowner_idnot found, or that user doesn't own a provider
Get Provider by Hash¶
Endpoint: GET /api/v1/admin/providers/{provider_hash}
Response 200 OK (ProviderResponse)
Error Responses:
404: Provider not found
Delete Provider¶
Endpoint: DELETE /api/v1/admin/providers/{provider_hash}
Response 204 No Content
Notes: Cascades to subscriptions this provider manages.
Error Responses:
404: Provider not found
Update Provider Status¶
Endpoint: PATCH /api/v1/admin/providers/{provider_hash}/status
Request Body (ProviderStatusUpdateRequest):
Response 200 OK (ProviderResponse)
Error Responses:
404: Provider not found
Update Provider URL¶
Endpoint: PATCH /api/v1/admin/providers/{provider_hash}/url
Request Body (ProviderURLUpdateRequest):
Response 200 OK (ProviderResponse)
Error Responses:
404: Provider not found422:invalid_url
Update Provider Name¶
Endpoint: PATCH /api/v1/admin/providers/{provider_hash}/name
Request Body (ProviderNameUpdateRequest):
Same pattern/length constraints as Create Provider's provider_name.
Response 200 OK (ProviderResponse)
Error Responses:
404: Provider not found409: New name already taken422: Invalid name pattern/length
Refresh Provider Token¶
Like Refresh User Token, the identifier goes in the body, not the URL.
Endpoint: POST /api/v1/admin/providers/refresh-token
Request Body (ProviderTokenRefreshRequest):
Response 200 OK (ProviderTokenRefreshResponse):
Error Responses:
404: Provider not found
Provider Authorization Management¶
Mounted under /api/v1/admin/providers/auth (nested inside the provider router above). This is the admin-side counterpart to the invite flow described in Provider API → Request Access to User and Authentication → Provider Connection-Invite HMAC.
Get Authorization Status¶
Endpoint: GET /api/v1/admin/providers/auth/{provider_name}/{user_id}
Response 200 OK (ProviderAuthorizationInfoResponse):
{
"provider_name": "my-provider",
"provider_url": "https://t.me/examplebot",
"user_id": 12345,
"status": "pending"
}
status is null if no authorization record exists between this provider and user.
Error Responses:
404: Provider not found, or user not found
Process Provider Connection Request¶
Finalizes a connection-invite link generated by Provider API → Request Access to User, verifying the embedded HMAC. Also used for a user that already exists (no HMAC needed in that case — see below).
Endpoint: POST /api/v1/admin/providers/auth
Request Body (ProviderAuthorizationRequest):
| Field | Required | Notes |
|---|---|---|
user_id |
Yes | Target user |
provider_name |
Yes | Pattern ^[a-z0-9]+(?:-[a-z0-9]+)*$ |
hmac |
No | 24-char hex digest from the invite link; required only when creating a new pending authorization for a user that has no prior authorization record with this provider |
Behavior:
- Looks up the provider by name first — an unknown
provider_namefails immediately, before touching any user record (so an invalid provider name can't be used to probe for user existence or create a user as a side effect). - Looks up (or creates, if missing) the user for
user_id. - If no authorization record exists yet and
hmacis provided, verifies it against(user_id, provider_hash)and creates apendingauthorization on success. - If an authorization already exists, its current status is returned as-is — this endpoint doesn't re-verify the HMAC or change status for an existing record.
Response 200 OK (ProviderAuthorizationInfoResponse)
Error Responses:
404: Provider not found401:hmacprovided but invalid (AuthenticationError— "Invalid or expired connection invite")
Approve Provider Connection¶
Endpoint: POST /api/v1/admin/providers/auth/approve
Request Body (ProviderAuthorizationDecisionRequest):
Behavior: if the authorization is already in a non-pending state, this endpoint returns the current status as-is (idempotent no-op) rather than erroring.
Response 200 OK (ProviderAuthorizationInfoResponse), status: "approved" (or the pre-existing status if it wasn't pending).
Error Responses:
404: Provider not found, user not found, or no authorization record exists at all
Reject Provider Connection¶
Endpoint: POST /api/v1/admin/providers/auth/reject
Request Body (ProviderAuthorizationDecisionRequest):
Behavior branches on subscription history:
- If the user already has subscriptions created by this provider, the authorization is set to
revoked(kept, not deleted) so those subscriptions remain traceable to a provider relationship — responsestatus: "revoked". - If the user has no subscriptions from this provider, the authorization record is deleted outright — response
status: null.
Response 200 OK (ProviderAuthorizationInfoResponse)
Error Responses:
404: Provider not found, user not found, or no authorization record exists
IP Ban Management¶
Ban IP Address¶
Endpoint: POST /api/v1/admin/bans
Request Body (IPBanRequest):
duration_seconds is optional; the server's default ban duration (3600 seconds / 1 hour) is used if omitted.
Response 201 Created (IPBanStatusResponse):
{
"ip_address": "192.168.1.100",
"is_banned": true,
"banned_until": "2026-04-27T11:00:00",
"remaining_seconds": 3600
}
Check Ban Status¶
Endpoint: GET /api/v1/admin/bans/{ip_address}
Response 200 OK (IPBanStatusResponse):
{
"ip_address": "192.168.1.100",
"is_banned": true,
"banned_until": "2026-04-27T11:00:00",
"remaining_seconds": 1800
}
banned_until is a plain ISO-8601 string (not a JSON datetime type distinction — treat it as a string on the wire either way).
Unban IP Address¶
Unlike most other admin single-resource deletes on this page, the IP address goes in the request body, not the URL path.
Endpoint: DELETE /api/v1/admin/bans
Request Body (IPUnbanRequest):
Response 200 OK (IPUnbanResponse):
was_banned: false if the address wasn't actually banned — safe to call unconditionally.
List All Bans¶
Endpoint: GET /api/v1/admin/bans
Response 200 OK (IPBanListResponse):
{
"entries": [
{ "ip_address": "192.168.1.100", "banned_until": "2026-04-27T11:00:00" }
],
"total": 1
}
Whitelist Management¶
Whitelisted IPs/ranges bypass rate limiting and take precedence over bans.
Add to Whitelist¶
Endpoint: POST /api/v1/admin/whitelist
Request Body (WhitelistAddRequest):
Response 201 Created (WhitelistAddResponse):
{
"ip_address": "10.0.0.0/24",
"description": "Office network",
"message": "IP added to whitelist"
}
List Whitelist¶
Endpoint: GET /api/v1/admin/whitelist
Response 200 OK (WhitelistListResponse):
{
"entries": [
{
"ip_address": "10.0.0.0/24",
"description": "Office network",
"added_at": "2026-04-27T10:00:00"
}
],
"total": 1
}
Remove from Whitelist¶
IP address goes in the request body, not the URL — same pattern as Unban IP Address.
Endpoint: DELETE /api/v1/admin/whitelist
Request Body (WhitelistRemoveRequest):
Response 200 OK (WhitelistRemoveResponse):
Usage Statistics¶
Endpoint: GET /api/v1/admin/stats
Query Parameters (all optional; defaults to all-time stats if none are given):
| Parameter | Type | Notes |
|---|---|---|
start_date |
datetime (ISO 8601) | — |
end_date |
datetime (ISO 8601) | — |
period |
"day" | "week" | "month" |
Predefined period, as an alternative to an explicit range |
Response 200 OK (StatsResponse):
Error Responses:
400:start_dateis afterend_date— note that this endpoint's error responses do not follow the standard{"error", "message", "details"}shape used elsewhere in the API (see Error Handling); the body here is FastAPI's default{"detail": "start_date cannot be after end_date"}, a plain string rather than a structured object.500: Statistics aggregation failed — same non-standard{"detail": "Failed to aggregate statistics"}shape.