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_frequency field 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