{"openapi":"3.1.0","info":{"title":"smtp2webhook control","version":"0.2.0","description":"Health, metrics, queue check, management page data and administration API of the smtp2webhook gateway. control speaks plain HTTP behind a proxy that ends HTTPS. The administration API answers to the addresses of CONTROL_API_ALLOW (the proxy) bearing a token; the management page data, read only, to those addresses alone.","license":{"name":"Apache-2.0","identifier":"Apache-2.0"}},"components":{"securitySchemes":{"token":{"type":"http","scheme":"bearer"}},"responses":{"Error":{"description":"Refused: off, address not allowed, bad token, rate limit, bad request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}},"schemas":{"Block":{"type":"object","properties":{"id":{"type":"integer"},"kind":{"enum":["sender","recipient"]},"value":{"type":"string","description":"address or @domain"},"reason":{"type":["string","null"]},"created_at":{"type":"number","description":"Unix time"},"expires_at":{"type":["number","null"],"description":"Unix time; null: permanent"}}},"NewBlock":{"type":"object","required":["kind","value"],"properties":{"kind":{"enum":["sender","recipient"]},"value":{"type":"string","maxLength":254,"description":"address or @domain"},"duration":{"type":"integer","minimum":1,"maximum":31536000,"description":"seconds; absent: permanent"},"reason":{"type":"string","maxLength":500}}},"FailedJob":{"type":"object","description":"A job of the failure queue, without its payload","properties":{"id":{"type":"string"},"queue_id":{"type":"string"},"sender":{"type":"string"},"recipient":{"type":"string"},"received_at":{"type":"string","format":"date-time"},"state":{"const":"failed"},"attempts":{"type":"integer"},"last_error":{"type":["string","null"]},"failed_at":{"type":["number","null"],"description":"Unix time"},"failure":{"type":["string","null"]}}},"RecentMessage":{"type":"object","description":"A message in control's memory: refused at RCPT TO, or handed to the delivery agent (one per recipient) and updated by each attempt. The envelope only, never the content.","properties":{"at":{"type":"number","description":"Unix time, first seen"},"updated":{"type":"number","description":"Unix time, last change"},"job":{"type":["string","null"]},"queue_id":{"type":["string","null"]},"client":{"type":["string","null"],"description":"client address"},"sender":{"type":["string","null"]},"recipient":{"type":["string","null"]},"status":{"enum":["refused","pending","delivered","deferred","failed"]},"detail":{"type":"string","description":"policy decision, or the attempt's reason"},"http_status":{"type":["integer","null"],"description":"webhook's answer"},"attempts":{"type":"integer"},"latency":{"type":["number","null"],"description":"seconds, last attempt"}}}}},"paths":{"/healthz":{"get":{"summary":"control is up","responses":{"200":{"description":"{\"status\": \"ok\"}"}}}},"/metrics":{"get":{"summary":"Prometheus metrics","responses":{"200":{"description":"Prometheus text format","content":{"text/plain":{}}}}}},"/check/queue":{"get":{"summary":"Queue check for a supervision that reads text","responses":{"200":{"description":"OK","content":{"text/plain":{}}},"503":{"description":"CRITICAL: <reasons>","content":{"text/plain":{}}}}}},"/openapi.json":{"get":{"summary":"This description","responses":{"200":{"description":"OpenAPI 3.1","content":{"application/json":{}}},"403":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/ui/overview.json":{"get":{"summary":"Data of the management page, read only","responses":{"200":{"description":"Figures, counts per minute, recent messages, failure queue, blocks","content":{"application/json":{"schema":{"type":"object","properties":{"now":{"type":"number"},"figures":{"type":"object"},"series":{"type":"object","properties":{"step":{"type":"integer","description":"seconds"},"start":{"type":"number","description":"Unix time"},"series":{"type":"object","additionalProperties":{"type":"array","items":{"type":"integer"}}}}},"recent":{"type":"array","items":{"$ref":"#/components/schemas/RecentMessage"}},"failed":{"type":"array","items":{"$ref":"#/components/schemas/FailedJob"}},"blocks":{"type":"array","items":{"$ref":"#/components/schemas/Block"}},"api":{"type":"boolean","description":"administration API on"},"recent_size":{"type":"integer"}}}}}},"403":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/api/v1/blocks":{"get":{"summary":"Blocks in force","security":[{"token":[]}],"responses":{"200":{"description":"Blocks","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Block"}}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}},"post":{"summary":"Block a sender or a recipient (550 at RCPT TO)","security":[{"token":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewBlock"}}}},"responses":{"201":{"description":"Block created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Block"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/api/v1/blocks/{block}":{"delete":{"summary":"Lift a block","security":[{"token":[]}],"parameters":[{"name":"block","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"204":{"description":"Lifted"},"404":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/api/v1/failed":{"get":{"summary":"Failure queue, without the payloads","security":[{"token":[]}],"responses":{"200":{"description":"Jobs","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FailedJob"}}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/api/v1/failed/{job}":{"get":{"summary":"A job of the failure queue, with its payload (written to the audit log)","security":[{"token":[]}],"parameters":[{"name":"job","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The job and its payload"},"404":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}},"delete":{"summary":"Delete a job of the failure queue","security":[{"token":[]}],"parameters":[{"name":"job","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"404":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/api/v1/failed/{job}/replay":{"post":{"summary":"Send the payload to the webhook again; removed from the queue if accepted","security":[{"token":[]}],"parameters":[{"name":"job","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Delivered"},"502":{"description":"The webhook refused it, or could not be reached"},"503":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}}}}