CertWatch — API Reference
Base URL: https://<your-domain>/api/
All endpoints require authentication via Authorization: Token <key> header unless marked as public.
Authentication
CertWatch uses token-based authentication. Each token (TenantToken) carries both user identity and tenant context. All data returned is scoped to the tenant associated with the token.
Authorization: Token 9a8b7c6d5e4f3a2b1c0d...
Rate Limiting
Auth endpoints (register, login, invite accept) are throttled to 5 requests per minute per IP.
Pagination
All list endpoints use page-based pagination:
| Parameter | Default | Max | Description |
|---|---|---|---|
page |
1 | — | Page number |
page_size |
20 | 500 | Results per page |
Response format:
{
"count": 142,
"next": "https://api.example.com/api/certificates/?page=2",
"previous": null,
"results": [...]
}
Authentication & Tenants — /api/auth/
Register
POST /api/auth/register/
Public — no authentication required.
Creates a new tenant, user (with is_active=False), and admin membership. Sends a verification email. The user must verify their email before they can log in.
Request:
{
"email": "user@example.com",
"password": "securePassword123",
"organization_name": "My Company"
}
Response (201):
{
"detail": "Verification email sent.",
"email": "user@example.com"
}
Note: No authentication token is returned at registration. The user must verify their email first, then log in to obtain a token.
Login
POST /api/auth/login/
Public — no authentication required.
Request:
{
"email": "user@example.com",
"password": "securePassword123",
"tenant_id": "uuid" // optional — omit to use most recent membership
}
Response (200):
{
"token": "9a8b7c6d...",
"email": "user@example.com",
"tenant_id": "uuid",
"tenant_name": "My Company",
"role": "admin"
}
Create Invitation
POST /api/auth/invitations/
Admin only. Creates an invitation for a new user to join the tenant.
Request:
{
"email": "newuser@example.com",
"role": "member" // "admin" or "member"
}
Response (201):
{
"invitation_id": "uuid",
"email": "newuser@example.com",
"role": "member",
"status": "pending",
"expires_at": "2025-01-22T10:00:00Z"
}
Accept Invitation
POST /api/auth/invite/accept/
Public. Accepts an invitation, creates a user account and returns a token.
Request:
{
"token": "invitation-token-string",
"password": "securePassword123"
}
Response (201):
{
"user_id": 2,
"tenant_id": "uuid",
"auth_token": "abc123..."
}
Verify Email
GET /api/auth/verify-email/?token=<token>
Public. Verifies a user's email address using the token from the verification email. Sets the user's account to active.
Response (200):
{
"detail": "Email verified successfully."
}
Returns 400 if the token is invalid or expired (tokens expire after 24 hours).
Resend Verification Email
POST /api/auth/resend-verification/
Public. Sends a new verification email for an unverified account.
Request:
{
"email": "user@example.com"
}
Response (200):
{
"detail": "Verification email sent."
}
Returns a success response regardless of whether the email exists (to prevent email enumeration).
List Members
GET /api/auth/members/
Admin only. Returns all members of the current tenant.
Member Detail
GET /api/auth/members/{id}/
PATCH /api/auth/members/{id}/ — update role
DELETE /api/auth/members/{id}/ — remove member (cannot remove last admin)
Admin only.
List / Create API Tokens
GET /api/auth/tokens/ — list tokens for current user + tenant
POST /api/auth/tokens/ — create a new API key token
Request (POST):
{
"name": "CI Pipeline Token",
"expires_at": "2026-01-01T00:00:00Z" // optional — null = never expires
}
Response (201):
{
"key": "abc123...",
"name": "CI Pipeline Token",
"created_at": "2025-01-15T10:00:00Z",
"expires_at": "2026-01-01T00:00:00Z"
}
Refresh Token
POST /api/auth/tokens/refresh/
Replaces the current token with a new one (fresh 30-day expiry). The old token is deleted.
Revoke Token
DELETE /api/auth/tokens/{key}/
Permanently deletes the specified token.
Organizations — /api/organizations/
List Organizations
GET /api/organizations/
Returns organizations with annotated certificate counts.
Query Parameters:
| Parameter | Description |
|---|---|
country |
Filter by ISO country code (e.g. ?country=DE) |
search |
Search by name or external_id (e.g. ?search=Acme) |
ordering |
Sort by name, external_id, country, created_at |
Response includes: id, external_id, name, website, country, active_certificates, expiring_certificates, expired_certificates
Create Organization
POST /api/organizations/
Request:
{
"external_id": "ORG-001",
"name": "Acme Corp",
"website": "https://acme.com",
"lei": "5493001KJTIIGC8Y1R12",
"vat": "DE123456789",
"country": "DE"
}
On creation, certificate discovery is automatically triggered in the background.
Organization Detail
GET /api/organizations/{id}/
PUT /api/organizations/{id}/
PATCH /api/organizations/{id}/
DELETE /api/organizations/{id}/
Detail response includes nested sites array.
Sites
GET /api/organizations/sites/
POST /api/organizations/sites/
GET /api/organizations/sites/{id}/
PUT /api/organizations/sites/{id}/
PATCH /api/organizations/sites/{id}/
DELETE /api/organizations/sites/{id}/
Request (POST):
{
"organization": "uuid",
"name": "Main Factory",
"address": "123 Industrial Way, Munich, Germany"
}
Certificates — /api/certificates/
List Certificates
GET /api/certificates/
Query Parameters:
| Parameter | Description |
|---|---|
organization |
Filter by organization UUID |
certificate_type |
Filter by certificate type UUID |
type_code |
Filter by type code (e.g. ?type_code=ISO_9001) |
status |
Filter by status: ACTIVE, EXPIRED, REVOKED, PENDING |
expiry_date |
Filter by exact expiry date |
expiry_date_before |
Certificates expiring on or before date |
expiry_date_after |
Certificates expiring on or after date |
search |
Search by certificate_number, issuing_body, scope |
ordering |
Sort by expiry_date, issue_date, status, created_at |
Create Certificate
POST /api/certificates/
Request:
{
"organization": "uuid",
"certificate_type_id": "uuid",
"certificate_number": "ISO-9001-2024-12345",
"issuing_body": "TÜV SÜD",
"scope": "Design and manufacturing of electronic components",
"issue_date": "2024-01-15",
"expiry_date": "2027-01-14",
"status": "ACTIVE",
"document_url": "https://example.com/cert.pdf"
}
Certificate Detail
GET /api/certificates/{id}/
PUT /api/certificates/{id}/
PATCH /api/certificates/{id}/
DELETE /api/certificates/{id}/
Response:
{
"id": "uuid",
"certificate_number": "ISO-9001-2024-12345",
"certificate_type": {
"code": "ISO_9001",
"name": "ISO 9001:2015"
},
"organization": {
"id": "uuid",
"external_id": "ORG-001",
"name": "Acme Corp"
},
"issuing_body": "TÜV SÜD",
"scope": "Design and manufacturing of electronic components",
"issue_date": "2024-01-15",
"expiry_date": "2027-01-14",
"status": "ACTIVE",
"document_url": "https://example.com/cert.pdf",
"source_url": null,
"document_hash": "a1b2c3...",
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T10:00:00Z"
}
Expiring Certificates
GET /api/certificates/expiring/?days=30
Returns active certificates expiring within the specified number of days (default: 30, max: 365).
Public Search
GET /api/certificates/search/?q=Acme
Public — no authentication required. Searches organizations by name/website and returns their certificates.
Certificate Types
GET /api/certificates/types/
POST /api/certificates/types/
GET /api/certificates/types/{id}/
PUT /api/certificates/types/{id}/
PATCH /api/certificates/types/{id}/
DELETE /api/certificates/types/{id}/
Monitoring — /api/monitoring/
Monitored Endpoints
GET /api/monitoring/endpoints/
POST /api/monitoring/endpoints/
GET /api/monitoring/endpoints/{id}/
PUT /api/monitoring/endpoints/{id}/
PATCH /api/monitoring/endpoints/{id}/
DELETE /api/monitoring/endpoints/{id}/
Query Parameters:
| Parameter | Description |
|---|---|
organization |
Filter by organization UUID |
is_active |
Filter by active status |
search |
Search by URL or organization name |
ordering |
Sort by url, last_checked, created_at, is_active |
Request (POST):
{
"organization_id": "uuid",
"url": "https://supplier.com/certificates",
"check_frequency": "12:00:00",
"is_active": true
}
Note: The
check_frequencyfield may be automatically adjusted by the system based on certificate expiry proximity. See Adaptive Scheduling below.
Detail response includes: nested organization object and recent_scrape_logs (last 5).
Trigger Manual Scrape
POST /api/monitoring/endpoints/{id}/trigger_scrape/
Queues an async scrape task. Returns 202 Accepted. Fails with 400 if endpoint is inactive.
Scrape Logs
GET /api/monitoring/logs/
GET /api/monitoring/logs/{id}/
Read-only audit trail of scrape attempts.
Certificate Discovery
POST /api/monitoring/discover/
POST /api/monitoring/discover/{organization_id}/
Triggers website crawling to discover certificate-related pages for an organization (or all organizations). Creates MonitoredEndpoint records for discovered URLs.
Monitoring Health
GET /api/monitoring/health/
Returns monitoring subsystem health status.
Adaptive Scheduling
MonitoredEndpoint check frequencies are automatically adjusted based on the expiry proximity of certificates linked to the endpoint's organization. After each scrape, the system evaluates the nearest certificate expiry date and updates check_frequency accordingly:
| Condition | Frequency | Rationale |
|---|---|---|
| Any certificate expiring within 7 days | Every 4 hours | Critical window — maximize freshness |
| Any certificate expiring within 30 days | Every 12 hours | Approaching expiry — increase vigilance |
| No certificates expiring within 30 days | Every 24 hours | Default baseline frequency |
The most urgent tier wins — if an organization has certificates in both the 7-day and 30-day windows, the 4-hour frequency is applied. This adjustment happens automatically after each scrape task completes; no manual API call is needed.
Webhooks — /api/webhooks/
See Webhooks Documentation for the full webhook system reference.
Subscribers
GET /api/webhooks/subscribers/
POST /api/webhooks/subscribers/
GET /api/webhooks/subscribers/{id}/
PUT /api/webhooks/subscribers/{id}/
PATCH /api/webhooks/subscribers/{id}/
DELETE /api/webhooks/subscribers/{id}/
Subscriptions
GET /api/webhooks/subscriptions/
POST /api/webhooks/subscriptions/
GET /api/webhooks/subscriptions/{id}/
PUT /api/webhooks/subscriptions/{id}/
PATCH /api/webhooks/subscriptions/{id}/
DELETE /api/webhooks/subscriptions/{id}/
Webhook Deliveries
GET /api/webhooks/deliveries/
GET /api/webhooks/deliveries/{id}/
Read-only delivery audit log.
Retry Failed Delivery
POST /api/webhooks/deliveries/{id}/retry/
Re-attempts delivery of a failed webhook. Only deliveries with status FAILED can be retried. Resets the delivery to PENDING and enqueues a new delivery task.
Response (200):
{
"status": "retry_queued",
"delivery_id": "delivery-uuid"
}
Returns 400 if the delivery is not in FAILED status.
Rotate Subscriber API Key
POST /api/webhooks/subscribers/{id}/rotate-key/
Generates a new api_key for the subscriber. The previous key is immediately invalidated. Subscriptions and delivery history are preserved.
Response (200):
{
"api_key": "new-auto-generated-api-key"
}
Rotate Subscriber Signing Secret
POST /api/webhooks/subscribers/{id}/rotate-signing-secret/
Generates a new signing_secret for HMAC signature verification. The previous secret is immediately invalidated.
Response (200):
{
"signing_secret": "new-auto-generated-signing-secret"
}
Imports — /api/imports/
Upload File
POST /api/imports/upload/
Multipart file upload. Accepts .xlsx, .xls, .csv.
Request: Content-Type: multipart/form-data with a file field.
Response (200 / 207):
{
"organizations_created": 5,
"organizations_updated": 2,
"certificate_types_created": 1,
"certificate_types_updated": 0,
"discovery_triggered": 5,
"errors": []
}
HTTP 207 indicates partial success (some rows imported, some failed).
Bulk Upload — /api/bulk-upload/
Upload Files
POST /api/bulk-upload/
Multipart upload. Send files under the files key. Accepted extensions: .pdf, .png, .jpg, .jpeg, .tiff, .webp. Max 20 MB per file.
Response (201):
{
"id": "batch-uuid",
"status": "pending",
"created_at": "2025-01-15T10:00:00Z",
"total_items": 3,
"file_errors": ["File 'doc.txt' has unsupported extension '.txt'..."]
}
Processing starts automatically in the background.
List Batches
GET /api/bulk-upload/
Returns all batches ordered by creation date (newest first).
Batch Detail
GET /api/bulk-upload/{id}/
Response:
{
"id": "uuid",
"status": "completed",
"created_at": "2025-01-15T10:00:00Z",
"total_items": 10,
"processed_count": 10,
"matched_count": 7,
"needs_review_count": 2,
"failed_count": 1,
"pending_count": 0,
"auto_match_rate": 0.7,
"failure_rate": 0.1
}
List Batch Items
GET /api/bulk-upload/{batch_id}/items/
Returns items with their status, extracted data, matched organization, and linked certificate.
Confirm Review
POST /api/bulk-upload/items/{item_id}/confirm/
Confirms a needs_review item. Creates a certificate and sets status to matched.
Request:
{
"organization_id": "uuid",
"certificate_type_code": "ISO_9001",
"certificate_number": "CERT-2024-001",
"issuing_body": "TÜV SÜD",
"scope": "Manufacturing",
"issue_date": "2024-01-15",
"expiry_date": "2027-01-14"
}
Skip Review
POST /api/bulk-upload/items/{item_id}/skip/
Skips a needs_review item. Sets status to skipped.
Serve File
GET /api/bulk-upload/files/{item_id}/
Returns the uploaded file with correct Content-Type header for preview.
Quality Metrics
GET /api/bulk-upload/quality-metrics/
Aggregate metrics across all batches: total processed, auto-match rate, failure rate, most-corrected fields.
Health Check
GET /api/health/
Public. Returns service health status.
Error Responses
All error responses follow a consistent format:
{
"error": "Description of what went wrong"
}
Validation errors return field-level details:
{
"email": ["A user with this email already exists."],
"password": ["This password is too common."]
}
HTTP Status Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 202 | Accepted (async task queued) |
| 207 | Multi-Status (partial success) |
| 400 | Bad Request (validation error) |
| 401 | Unauthorized (missing or invalid token) |
| 403 | Forbidden (insufficient permissions) |
| 404 | Not Found |
| 429 | Too Many Requests (rate limited) |
| 500 | Internal Server Error |