This route shares bot filtering and the buffer with the JSON access records on Server events. Lines classified as human do not enter the event table. The key scope must be logs:write; events:write is not enough.
Endpoint
https://app.tapcub.com/api/v1/server/logs/import Import combined log lines
Bearer API key with scope logs:write. Each line is an Nginx or Apache combined log line (max 4000 characters, 1–5000 lines). Unparsed lines are skipped. Only bot lines are stored. NestJS returns 201. Errors: 400 VALIDATION, 401 NO_API_KEY, 401 BAD_API_KEY.
Request body application/jsonrequired
linesstring[]requiredResponses
- 201Parse counts
- 400Body failed schema validation (VALIDATION)
- 401Missing or rejected API key (NO_API_KEY, BAD_API_KEY)
Response fields 201
linesintegerparsedintegeracceptedintegerskippedintegerParsed lines that were not bots
The project is the one bound to the key, so do not send a separate project id. A user-level key with no project is rejected.
{
"lines": [
"203.0.113.8 - - [02/Oct/2026:08:01:02 +0800] \"GET /robots.txt HTTP/1.1\" 200 120 \"-\" \"Googlebot/2.1\" 0.012"
]
}The parser accepts combined format only: IP, two placeholders, a bracketed time, the request line, a three-digit status, bytes, a quoted referrer, and a quoted user agent. An optional trailing duration in seconds becomes responseTime in milliseconds. Times use the offset in the log line, and months are English abbreviations. A user agent of - is classified as an empty string.
The 5000-line cap is a validation limit, not a recommended batch size. The 2 MB JSON body limit still applies.
Response
{ "lines": 1, "parsed": 1, "accepted": 1, "skipped": 0 }lines: how many lines were posted.parsed: how many matched combined format.linesgreater thanparsedmeans the format did not match; it has nothing to do with bot filtering.accepted: how many were classified as bots and buffered.skipped:parsedminusaccepted, the lines that parsed but were not bots.