Logs, metrics and health¶
What the edge saw, read-only: the request log per vService, its traffic metrics, the same metrics summed over all your vServices, and their health. They have the lower request limit, see API access.
Endpoints¶
| Method | Path | Key | Purpose |
|---|---|---|---|
GET |
/metrics |
read | Traffic summed over all vServices you can see |
GET |
/services/{fqdn}/logs |
read | Request log: one aggregate or raw lines |
GET |
/services/{fqdn}/logs/export |
read | Export the request log (streamed) |
GET |
/services/{fqdn}/logs/l4 |
read | All aggregates of the L3/4 forensics view |
GET |
/services/{fqdn}/logs/l4/raw |
read | Newest raw L3/4 lines |
GET |
/services/{fqdn}/logs/overview |
read | All aggregates of the forensics view |
GET |
/services/{fqdn}/logs/timeseries |
read | Requests over time by a field |
GET |
/services/{fqdn}/metrics |
read | Traffic metrics of a vService |
GET /metrics
| Parameter | In | Meaning |
|---|---|---|
metric |
query | Chart; traffic_by_service is the current bytes/s per vService; one of traffic, response_codes, protection, pre_http_filter, l4_packets, l4_flows, traffic_by_service; default "traffic" |
range |
query | Time window; one of 5m, 15m, 1h, 6h, 24h, 7d; default "1h" |
delegation |
query | Narrow to one delegation you can see |
Returns: 200, MetricChart or TrafficByService
Answer 200: traffic
{
"metric": "traffic",
"range": "1h",
"step": "15s",
"series": [
{
"label": "at_site",
"values": [
[
1792750389,
1.5
],
[
1792750404,
2.0
]
],
"dashed": true,
"direction": "in"
},
{
"label": "inbound",
"values": [
[
1792750389,
1.5
],
[
1792750404,
2.0
]
],
"direction": "in"
},
{
"label": "passed_frontend",
"values": [
[
1792750389,
1.5
],
[
1792750404,
2.0
]
],
"direction": "in"
}
],
"scope": {
"delegation": null,
"services": 3,
"has_http": true,
"has_l4": true
}
}
Answer 200: traffic_by_service
{
"metric": "traffic_by_service",
"window": "5m",
"unit": "bytes/s",
"data": [
{
"fqdn": "a.example.com",
"inbound": 2.0,
"outbound": 2.0
},
{
"fqdn": "deleg.example.com",
"inbound": 0.0,
"outbound": 0.0
},
{
"fqdn": "tcp.example.com",
"inbound": 0.0,
"outbound": 0.0
}
],
"scope": {
"delegation": null,
"services": 3,
"has_http": true,
"has_l4": true
}
}
GET /services/{fqdn}/logs
| Parameter | In | Meaning |
|---|---|---|
type |
query | An aggregate, or raw for the newest lines; one of raw, status_codes, threat_intel, top_asns, top_combination, top_countries, top_deny_rules, top_host_headers, top_http_methods, top_http_versions, top_ips, top_ja4, top_path_class, top_rules, top_uris, top_user_agents; default "top_ips" |
range |
query | Time window; one of 5m, 15m, 1h, 6h, 24h, 96h; default "1h" |
limit |
query | Rows; 1–500; default 10 |
view |
query | allowed: what reached your origin; blocked: what the edge turned away; all: both; one of allowed, blocked, all; default "allowed" |
from |
query | Start of the window, RFC 3339, at most 96 h back; with only from, range sets the length; type=raw only |
to |
query | End of the window, RFC 3339, not included; with only to, range sets the length. Page back by passing the oldest timestamp you received; type=raw only |
client_ip |
query | Lines whose client address contains this; type=raw only |
status_code |
query | Lines whose status code contains this: 403, or 40 for 400 to 409; type=raw only |
uri |
query | Lines whose path and query string contain this; type=raw only |
ja4 |
query | Lines whose TLS fingerprint contains this; type=raw only |
Returns: 200, LogTop or LogRaw
Answer 200: top_ips
{
"type": "top_ips",
"range": "1h",
"field": "client_ip",
"window_seconds": 3600,
"service_total": 7,
"service_total_rps_avg": 0.0019444444444444444,
"data": [
{
"value": "192.0.2.7",
"count": 7,
"rps_avg": 0.0019444444444444444,
"share": 1.0
}
]
}
Answer 200: raw
{
"type": "raw",
"range": "1h",
"count": 1,
"data": [
{
"timestamp": "2026-10-23T10:13:24.000000000Z",
"line": "client_ip=192.0.2.7 client_port=40000 frontend_name=ft_a_example_com backend_name=bk_a_example_com server_name=origin-1 tq=0 tw=0 tc=1 tr=5 ta=6 status_code=200 bytes_read=512 termination_state=-- zp_cache=MISS feconn=3 beconn=2 srv_conn=1 retries=0 srv_queue=0 backend_queue=0 http_method=GET http_request_uri=/a"
}
]
}
GET /services/{fqdn}/logs/export
| Parameter | In | Meaning |
|---|---|---|
format |
query | csv: one column per field; logfmt: key=value per line; one of csv, logfmt; default "csv" |
range |
query | Time window; one of 5m, 15m, 1h, 6h, 24h, 96h; default "1h" |
from |
query | Start of the window, RFC 3339, at most 96 h back; with only from, range sets the length |
to |
query | End of the window, RFC 3339, not included; with only to, range sets the length. Page back by passing the oldest timestamp you received |
client_ip |
query | Lines whose client address contains this |
status_code |
query | Lines whose status code contains this: 403, or 40 for 400 to 409 |
uri |
query | Lines whose path and query string contain this |
ja4 |
query | Lines whose TLS fingerprint contains this |
Returns: 200, a file (text/csv, text/plain)
Answer 200 (text/csv)
{
"code": "not_found",
"error": "Service not found: a.example.com"
GET /services/{fqdn}/logs/l4
| Parameter | In | Meaning |
|---|---|---|
range |
query | Time window; one of 5m, 15m, 1h, 6h, 24h, 96h; default "1h" |
view |
query | allowed: UDP flows and TCP connections; dropped: sources the edge dropped; one of allowed, dropped; default "allowed" |
limit |
query | Rows per aggregate; 1–500; default 50 |
Returns: 200, L4Overview
Answer 200: allowed
{
"range": "1h",
"view": "allowed",
"window_seconds": 3600,
"since": "2026-10-23T09:13:25+00:00",
"protocols": [
"tcp",
"udp"
],
"panels": {
"top_countries": {
"field": "geoip_country_code",
"data": [
{
"value": "DE",
"count": 14,
"label": "DTAG"
}
]
},
"top_clients": {
"field": "client_ip",
"data": [
{
"value": "192.0.2.7",
"count": 14,
"bytes": 14,
"label": "DTAG"
}
]
},
"top_asns": {
"field": "geoip_asn",
"data": [
{
"value": "3320",
"count": 14,
"label": "DTAG"
}
]
},
"termination": {
"field": "termination_state",
"data": [
{
"value": "--",
"count": 14
}
]
},
"top_origins": {
"field": "server_name",
"data": [
{
"value": "origin-1",
"count": 14,
"bytes": 14,
"label": "DTAG"
}
]
},
"summary": {
"udp_flows": 7,
"udp_clients": 7,
"tcp_connections": 7,
"tcp_clients": 7,
"bytes_to_client": 14,
"bytes_to_origin": 14
},
"top_ports": {
"field": "vip_port",
"data": [
{
"value": "5000",
"count": 7,
"label": "DTAG"
}
]
},
"packet_shape": {
"available": true,
"samples": {
"5m": 2
},
"windows": {
"5m": {
"dport": [
{
"value": "443",
"share": 2.0,
"service_port": true
}
]
}
}
}
},
"errors": {},
"took_ms": 0
}
GET /services/{fqdn}/logs/l4/raw
| Parameter | In | Meaning |
|---|---|---|
range |
query | Time window; one of 5m, 15m, 1h, 6h, 24h, 96h; default "1h" |
view |
query | allowed: UDP flows and TCP connections; dropped: sources the edge dropped; one of allowed, dropped; default "allowed" |
limit |
query | Lines; 1–500; default 200 |
ip |
query | Client or source address, prefix match |
Returns: 200, L4Raw
Answer 200: allowed
{
"range": "1h",
"view": "allowed",
"since": "2026-10-23T09:13:25+00:00",
"count": 1,
"data": [
{
"timestamp": "2026-10-23T10:13:24.000000000Z",
"fields": {
"proto": "tcp",
"client_ip": "192.0.2.7",
"client_port": "40000",
"server_name": "origin-1",
"termination_state": "--",
"duration_s": "3",
"bytes_read": "10",
"bytes_uploaded": "20"
}
}
]
}
GET /services/{fqdn}/logs/overview
| Parameter | In | Meaning |
|---|---|---|
range |
query | Time window; one of 5m, 15m, 1h, 6h, 24h, 96h; default "1h" |
limit |
query | Rows per aggregate; 1–500; default 50 |
view |
query | allowed: what reached your origin; blocked: what the edge turned away; all: both; one of allowed, blocked, all; default "allowed" |
types |
query | Comma-separated subset of top_countries, top_ips, top_asns, top_host_headers, top_uris, top_user_agents, top_http_methods, top_http_versions, status_codes, top_ja4, top_path_class, top_rules, top_deny_rules, threat_intel, top_combination; default all |
Returns: 200, LogOverview
Answer 200
{
"range": "1h",
"view": "allowed",
"window_seconds": 3600,
"service_total": 7,
"service_total_rps_avg": 0.0019444444444444444,
"types": [
"top_ips",
"top_countries"
],
"panels": {
"top_ips": {
"type": "top_ips",
"range": "1h",
"field": "client_ip",
"window_seconds": 3600,
"service_total": 7,
"service_total_rps_avg": 0.0019444444444444444,
"data": [
{
"value": "192.0.2.7",
"count": 7,
"rps_avg": 0.0019444444444444444,
"share": 1.0
}
]
},
"top_countries": {
"type": "top_countries",
"range": "1h",
"field": "geoip_country_code",
"window_seconds": 3600,
"service_total": 7,
"service_total_rps_avg": 0.0019444444444444444,
"data": [
{
"value": "DE",
"count": 7,
"rps_avg": 0.0019444444444444444,
"share": 1.0
}
]
}
},
"errors": {},
"took_ms": 0
}
GET /services/{fqdn}/logs/timeseries
| Parameter | In | Meaning |
|---|---|---|
field |
query | One line per value; one of geoip_country_code, http_method, http_version, status_code, user_agent, zp_cache; default "status_code" |
range |
query | Time window; one of 5m, 15m, 1h, 6h, 24h, 96h; default "1h" |
limit |
query | Lines, the busiest values; 1–50; default 10 |
Returns: 200, LogTimeseries
Answer 200
{
"type": "timeseries",
"field": "status_code",
"range": "1h",
"step": 60,
"series": [
{
"label": "200",
"values": [
[
1792750344,
3
],
[
1792750404,
4
]
]
}
]
}
GET /services/{fqdn}/metrics
| Parameter | In | Meaning |
|---|---|---|
metric |
query | summary: live values; origin_status: health check per origin; the others: a chart; one of backend_connections, backend_request_rate, backend_response_codes, backend_traffic, blocked, l4_flows, l4_packets, origin_status, pre_http_filter, protection, request_rate, request_rate_blocked, response_codes, response_time, summary, traffic, upstream; default "summary" |
range |
query | Time window; one of 5m, 15m, 1h, 6h, 24h, 7d; default "1h" |
Returns: 200, MetricChart or MetricSummary or OriginStatus
Answer 200: summary
{
"metric": "summary",
"data": {
"requests_per_sec": 2.0,
"packets_per_sec": 2.0,
"bits_per_sec": 2.0,
"bits_out_per_sec": 2.0,
"active_sessions": 4,
"active_sessions_tcp": 2,
"active_flows_udp": 2,
"healthy_backends": 2,
"connections_rejected_per_sec": 2.0,
"edge_bytes": 2,
"origin_bytes": 2
}
}
Answer 200: traffic
{
"metric": "traffic",
"range": "1h",
"step": "15s",
"series": [
{
"label": "at_site",
"values": [
[
1792750389,
1.5
],
[
1792750404,
2.0
]
],
"dashed": true,
"direction": "in"
},
{
"label": "inbound",
"values": [
[
1792750389,
1.5
],
[
1792750404,
2.0
]
],
"direction": "in"
},
{
"label": "passed_frontend",
"values": [
[
1792750389,
1.5
],
[
1792750404,
2.0
]
],
"direction": "in"
}
]
}
Answer 200: origin_status
{
"metric": "origin_status",
"data": [
{
"name": "origin-1",
"up_edges": 2,
"edges": 2
}
]
}
| Method | Path | Key | Purpose |
|---|---|---|---|
GET |
/services/health |
read | Health of all your vServices |
GET /services/health
Returns: 200, HealthOverview
Answer 200
{
"summary": {
"total": 3,
"worst": "unknown",
"counts": {
"blackholed": 0,
"origin_down": 0,
"misconfigured": 0,
"errors": 0,
"origin_slow": 0,
"under_attack": 0,
"normal": 0,
"maintenance": 0,
"disabled": 0,
"unknown": 3
}
},
"services": {
"a.example.com": {
"status": "unknown",
"fault": "none",
"reason": "warming_up",
"signals": {
"requests_per_sec": 2.0,
"errors_per_sec": 2.0,
"error_ratio": 1.0,
"origin_response_seconds": null,
"origins_up": null,
"origins_total": null
}
},
"deleg.example.com": {
"status": "unknown",
"fault": "none",
"reason": "warming_up",
"signals": {
"requests_per_sec": 0.0,
"errors_per_sec": 0.0,
"error_ratio": 0.0,
"origin_response_seconds": null,
"origins_up": null,
"origins_total": null
}
},
"tcp.example.com": {
"status": "unknown",
"fault": "none",
"reason": "dns_not_measured",
"signals": {
"requests_per_sec": 0.0,
"errors_per_sec": 0.0,
"error_ratio": 0.0,
"origin_response_seconds": null,
"origins_up": null,
"origins_total": null
}
}
}
}
Answers¶
ServiceHealth¶
| Field | Type | Notes |
|---|---|---|
status (required) |
string | one of blackholed, origin_down, misconfigured, errors, origin_slow, under_attack, normal, maintenance, disabled, unknown; normal: nothing found. under_attack: the protection is stopping attack traffic; not a fault, nothing to do. errors: 5 % or more of the answers are 5xx, at least 1 per second; fault says whether the edge or your origin makes them. origin_slow: your origins take 1 s or more on average. origin_down: no origin passes its health check. misconfigured: a hostname's DNS does not point at ZERO-PROTECT (GET /services/{fqdn}/dns), or there is neither an origin nor a redirect. blackholed: Zero Services had the address blackholed upstream to protect the site's line from a flood; nothing arrives until that ends. maintenance: maintenance_mode is on. disabled: the vService is switched off. unknown: not measured right now, such as just after a restart; not an error |
fault (required) |
string | one of zp, customer, none; zp: our side; customer: origin, DNS or configuration |
reason (required) |
string | Machine-readable, empty when normal |
signals (required) |
HealthSignals |
The measurements behind the state |
MetricChart¶
| Field | Type | Notes |
|---|---|---|
metric (required) |
string | The chart asked for |
range (required) |
string | one of 5m, 15m, 1h, 6h, 24h, 7d; Time window |
step (required) |
string | Distance between two points |
series (required) |
list of MetricSeries |
One series per line |
meta |
object | metric=upstream only: flow at the site boundary is sampled |
scope |
MetricScope |
GET /metrics and pool metrics only: what was summed |
MetricSummary¶
| Field | Type | Notes |
|---|---|---|
metric (required) |
string | one of summary; Always summary |
data (required) |
object | The values |
LogTop¶
| Field | Type | Notes |
|---|---|---|
type (required) |
string | The aggregate asked for |
range (required) |
string | one of 5m, 15m, 1h, 6h, 24h, 96h; Time window |
field |
string | The log field ranked |
fields |
list of string | top_combination only: the fields of a combination |
window_seconds (required) |
integer | Length of the range in seconds |
service_total |
integer | may be null; Requests of the vService in the window, in the chosen view |
service_total_rps_avg |
number | service_total divided by window_seconds |
total |
integer | threat_intel only: requests from listed sources |
total_rps_avg |
number | threat_intel only |
total_share |
number | may be null; threat_intel only |
data (required) |
list of LogRow |
Rows, busiest first |
LogRow¶
| Field | Type | Notes |
|---|---|---|
value |
string | The field's value; Unknown when the line carries none |
label |
string | Application of a JA4 fingerprint, name of a threat-intel set |
fields |
map of string | top_combination only: the six fields of the combination |
count (required) |
integer | Requests in the window |
rps_avg (required) |
number | count / window_seconds |
share |
number | may be null; Part of the vService's requests in the chosen view, 0 to 1 |
LogRaw¶
| Field | Type | Notes |
|---|---|---|
type (required) |
string | one of raw; Always raw |
range (required) |
string | one of 5m, 15m, 1h, 6h, 24h, 96h; Time window |
count (required) |
integer | Lines returned |
data (required) |
list of object | Lines, newest first |
Example¶
Top blocked addresses in the last hour, then the raw log of that hour as CSV:
curl -s -H "Authorization: Bearer $ZP_KEY" \
"$API/services/www.example.com/logs?view=blocked&type=top_ips&range=1h&limit=20"
curl -s -H "Authorization: Bearer $ZP_KEY" -o log.csv \
"$API/services/www.example.com/logs/export?format=csv&range=1h"
Raw lines of a past hour, then the hour before it: to takes the oldest timestamp of
the previous page.
curl -s -H "Authorization: Bearer $ZP_KEY" \
"$API/services/www.example.com/logs?type=raw&limit=500&from=2026-10-23T05:00:00Z&to=2026-10-23T06:00:00Z"
curl -s -H "Authorization: Bearer $ZP_KEY" \
"$API/services/www.example.com/logs?type=raw&limit=500&to=2026-10-23T05:41:07.120834512Z"
Behaviour¶
view:allowed(default) is what reached your application,blockedwhat mitigation stopped,allboth. A drop inallowedmeans your users are affected; a spike inblockedalone is the protection working.- Logs reach back 96 hours, metrics 7 days. Request-level data is kept 4 days: export what you need longer.
fromandto(RFC 3339) choose a window for raw lines and the export, within the last 96 hours;tois not included. With only one of them,rangegives the length.- Each aggregate row has its rate in requests per second and its share of all traffic to the service in the range, not of the list.
- Request paths fold runs of digits into
#(/live/c#/seg_hd_#.ts). - Connections stopped before HTTP (most of a flood) are not in the log; the metrics count them as connections, not requests. Never add the two.
- The edge request total is your request rate; origin figures are always lower, since cached, challenged and blocked requests never reach it.
- Origin connections are shown for the busiest edge, against your
maxconn, which applies per edge (origins). - Dedicated sites with flow export also get traffic at the site boundary, per address and attack class, including what was already dropped. It is sampled (right in the Gbit/s range) and is never comparable with the edge figures, see sites.
All your vServices¶
GET /metrics sums the same metrics over every vService the key can see: the whole account
for an account key, only its delegation for a delegation key. delegation= narrows to one
delegation you can see; any other name is 404.
curl -s -H "Authorization: Bearer $ZP_KEY" "$API/metrics?metric=traffic&range=24h"
curl -s -H "Authorization: Bearer $ZP_KEY" "$API/metrics?metric=traffic_by_service"
traffichasinbound,passed_frontend,scrubbedandoutboundin bytes per second, all positive; each series says itsdirection(inorout). Drawoutbelow the axis if you mirror the chart.traffic_by_serviceis the current inbound and outbound of each vService, averaged over 5 minutes. One vService dropping to 0 while the others carry on is what the sum hides.scopein the answer counts the vServices summed and says whether HTTP and layer 3/4 metrics apply.- From 6 hours up, each point is the peak of its interval, so a short burst stays visible.
In the portal: ZERO-PROTECT → Overview, charts Frontend (Edge) for all services, one delegation or one service.
Layer 3/4¶
For vServices with TCP or UDP ports outside HTTP, GET /services/{fqdn}/logs/l4 gives the same
kind of aggregates below HTTP, and GET /services/{fqdn}/logs/l4/raw the newest lines.
view=allowed: the UDP flows the edge forwarded and the TCP connections, by client, country, AS, port and origin, and how they ended. A UDP flow appears when it ends, and every 30 minutes while it runs; its bytes are the flow's total.view=dropped: the sources the edge dropped, by reason, destination port and protocol. One entry is one source per period, not one packet.- Packets per second by verdict and the open flows and sessions are the metrics
l4_packetsandl4_flows. - Mode
tcplists TCP connections only with access logging on; UDP flows and drops always.
In the portal: Forensics → Layer 3/4 and the Metrics tab.
Log fields¶
The export (csv or logfmt) and raw lines carry:
| Field | Meaning |
|---|---|
client_ip, client_port |
the source |
geoip_country_code, geoip_asn, geoip_asn_org |
where it is from |
http_hostname, http_method, http_request_uri, http_version |
the request |
status_code, bytes_read |
the answer |
user_agent, ja4 |
claimed client, and its real TLS stack |
tq, tw, tc, tr, ta |
timings: request, queue, connect, response, total |
termination_state |
how the connection ended |
feconn, retries |
connections on the frontend, origin retries |
pow_passed, force_pow |
proof-of-work solved, and whether it was demanded |
coraza_action, coraza_failed |
WAF verdict, and whether the WAF itself failed |
ti_hit |
threat-intelligence list that matched |
path_class |
auth, admin or unset |
pr_hit |
the first rule that matched |
pr_deny |
the rule that stopped the request; empty when nothing did |
Why a request was blocked¶
Read pr_deny. pr_hit can differ: an earlier challenge rule keeps pr_hit while a later
rule blocks.
pr_deny / pr_hit |
Cause |
|---|---|
<name>-deny, -block, -drop, -challenge |
your protection rule <name> (rules) |
rate-conn, rate-http, rate-path, rate-pathagg, rate-ja4, rate-soft |
a rate limit |
rate-botcheck, rate-botcheck-agg |
the proof-of-work endpoint's own limit, per source or per service |
host-mismatch |
421: the Host header is not a hostname of this service |
ti-deny, ti-abuse, ti-tor |
threat intelligence |
geo-blocklist, asn-blocklist, ip-blocklist, ja4-blocklist |
platform-wide blocklist |
origin-busy |
your origin's queue was full: 503 at once (origins) |
token-unknown |
token gate: too many unconfirmed tokens from one source, 429 (cache) |
empty, with coraza_action=deny |
the WAF, never a geo or rate decision |
Health states¶
| Health | Meaning | Who acts |
|---|---|---|
| Normal | nothing stands out | — |
| Under attack | attack traffic is being stopped; not a fault | nobody |
| Errors | visitors get errors | us if they start at our edge, you if your origin returns them |
| Origin down | no origin passes its health check | you |
| Origin slow | your origin answers slowly | you |
| Misconfigured | a hostname's DNS does not point at us (see GET /services/{fqdn}/dns), or no origin and no redirect |
you |
| Maintenance | maintenance mode is on | — |
| Disabled | the service is switched off | — |
| Unknown | no reliable measurement just now; not an error | nobody |
enabled means switched on, not healthy.
Reports¶
Support makes reports over a period you choose on request, normally on the next business day: name the period, one service or all, and the purpose. They reach back 90 days for aggregated metrics, 4 days for request-level detail.
In the portal: Forensics and Metrics per service, the Health column in the service list.