CorebanqCorebanq Developer Docs
KYTv1

Read both KYT sides of one transaction

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.

GET
/v1/kyt/{transaction_id}

Authorization

bearerAuth
AuthorizationBearer <token>

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

transaction_id*string

Must parse as a uuid. It is re-serialised through uuid.Parse before use, so a differently-cased or braced form is normalised.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/v1/kyt/497f6eca-6276-4993-bfeb-53cbbbba6f08"
{
  "sender_transaction": {
    "datetime": "2019-08-24T14:15:22Z",
    "label": "string",
    "metadata": [
      {}
    ],
    "status": "string",
    "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",
    "created_at": "2019-08-24T14:15:22Z",
    "transaction_id": "0fec1e58-b197-4052-99cf-2218496c5482"
  },
  "recipient_transaction": {
    "datetime": "2019-08-24T14:15:22Z",
    "label": "string",
    "metadata": [
      {}
    ],
    "status": "string",
    "customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",
    "created_at": "2019-08-24T14:15:22Z",
    "transaction_id": "0fec1e58-b197-4052-99cf-2218496c5482"
  }
}
{
  "status": 400,
  "message": "Invalid input",
  "code": "common.invalid_input",
  "class": "validation"
}
{
  "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": "Error getting transaction",
  "code": "complyadvantage_m.get_transaction",
  "class": "business"
}

{
  "overall_status": "unhealthy",
  "message": "Service is shutting down",
  "timestamp": "2026-08-27T15:04:05Z"
}

GETRead 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.

GETList crypto screenings

THE customer_id AND provider FILTERS DO NOT WORK. The handler looks for those keys in the map the shared parser returns, but the parser's switch recognises only limit, offset, sort, stack, distinct, filter, search, search_text and fill_gaps, and search.<field> keys never reach it at all — ParseQueryParameters tests the search. prefix and continues first. Everything else hits default and is dropped. Both keys are therefore always absent, both branches are dead, and every call falls through to the list-all path. Asking for one customer's screenings returns every customer's, with a 200 and no warning. They are documented below as they are declared in the handler, marked inert, because removing them from the spec would hide the divergence rather than record it. Only limit, offset and sort survive into the query.