Read one crypto screening
Reads the stored row. screened_at is ALWAYS a real timestamp on both routes. Both writers persist ScreenedAt from the driver result — mapTransactionResponse seeds it with time.Now().UTC() before any provider override — and they do so for PROCESSING and PENDING rows too; neither UpdateStatus nor UpdateFromPolling clears the column. Every row these two writers create carries a real timestamp, so neither a zero time nor a null is a not-yet-screened signal — use status. The two routes DO differ in how a null would surface, though: this one runs the column through timeValue, which renders a nil as 0001-01-01T00:00:00Z, while the list route passes the *time.Time straight through and emits null. The column is nullable in the migration, so a row from any other source shows the difference. NEITHER FAILURE ON THIS ROUTE IS THE STATUS IT SHOULD BE — see the 500.
Authorization
bearerAuth Every route in this module is registered through auth.WrapWithMiddlewares, so all six require a bearer token; none is public. The token is the one the authentication module issues. A missing, unparseable or blacklisted token is the 401 described by Unauthorized401 — and with the rate limiter on, so is a token whose holder lacks the endpoint grant.
In: header
Path Parameters
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/kyt/crypto/screenings/497f6eca-6276-4993-bfeb-53cbbbba6f08"{
"screening_id": "c21341e0-a82a-41e6-9578-cd022db5aa1a",
"provider": "string",
"provider_ref_id": "string",
"status": "string",
"action": "string",
"kyt_flag": "string",
"risk_score_level": 0,
"risk_score_label": "string",
"alert_count": 0,
"alerts": [
{
"alert_id": "string",
"rule_id": "string",
"status": "string",
"risk_score_level": 0,
"risk_score_label": "string",
"category": "string",
"message": "string",
"issued_at": "2019-08-24T14:15:22Z"
}
],
"screened_at": "2019-08-24T14:15:22Z"
}{
"status": 401,
"message": "Unauthorized",
"code": "common.unauthorized",
"class": "business"
}{
"status": 403,
"message": "No access to the record",
"code": "common.rbac_no_rec_access",
"class": "business"
}{
"status": 429,
"message": "Rate limit for 203.0.113.7 to GET:/v1/kyt exceeded.",
"code": "rate_limits_m.exceeded",
"class": "temporary",
"retryable": true
}{
"status": 500,
"message": "Record not found",
"code": "common.record_not_found",
"class": "business"
}{
"overall_status": "unhealthy",
"message": "Service is shutting down",
"timestamp": "2026-08-27T15:04:05Z"
}Health and progress of the background poller. IT FINISHES NOTHING UNDER THE DEFAULT DRIVER: processScreening calls GetScreening with the record's provider_ref_id, which the mock set to the original transaction id, and CryptoMockService re-derives the status from that same id prefix — so a row screened as processing or pending returns non-final forever, UpdateFromPolling never runs, and pending_count only grows. The route's purpose holds under xziel alone. Takes no parameters and cannot fail on its own. It answers 200 with {"enabled": false, "pending_count": 0} when GetPollerStats returns nil, which happens only when no poller was constructed — see the enabled property. A LIVE poller is a separate matter: its pending_count is not a guaranteed count, because Stats keeps the field at zero when CountPending errors, so {"enabled": true, "pending_count": 0} during a database outage looks exactly like a drained queue.
Reads kyt.transactions twice, for "<transaction_id>_sender" and "<transaction_id>_recipient". The path segment is the BASE id — the suffixes are appended by the repository and must not be sent. AN UNKNOWN ID IS 200 null, NOT 404. When neither side exists the repository returns (nil, nil) — no error — and the handler serialises that nil straight out, so the body is the four bytes null. A client that dereferences the response without a nil check breaks here, and one that treats any 200 as "found" reports a screening that does not exist. NOT A CONSISTENT SNAPSHOT. GetKYTTransactions issues two independent First queries with no surrounding transaction, so a response can pair a sender side read before a write with a recipient side read after it.