Introduction
This Developer Portal is the integration reference for the FT AML Platform. It's written for three audiences: customers connecting their own core banking or onboarding systems to FT AML, partners building on top of the platform on a customer's behalf, and Fyscaltech implementation teams delivering a go-live. Wherever a detail depends on your specific deployment — credentials, exact payload schemas, rate limits — this guide says so explicitly rather than guessing, and points you to the Fyscaltech integration team who confirms it during onboarding.
What FT AML does
FT AML is an Anti-Money Laundering (AML) platform that helps financial institutions detect suspicious activity, screen customers against regulatory watchlists, monitor transactions, manage investigations, and comply with AML/CTF regulations. The Developer Portal covers the integration surface of that platform — getting your data in, and getting screening results and alerts back out — rather than the compliance-analyst console itself.
There are three ways data moves between your systems and FT AML
- Bulk CSV via S3 — for loading or refreshing large volumes of Customer, Customer Account, and Customer Transaction records.
- Customer Ingestion API — for pushing or updating individual customer records from your own systems as they change.
- Screening API — for screening a specific customer record on demand, by full name.
The CSV file format for Customer and Account records, and the Customer Ingestion API and Screening API request/response reference, are now fully documented in Integration Methods. Still pending: webhook payload format and signature verification guidelines, a Postman collection, and an OpenAPI specification — will be published soon once received from technical team.
Who does what
Customers
Own the source data (customers, accounts, transactions) and decide which integration method(s) fit their volume and architecture.
Partners
Build and operate an integration on a customer's behalf, typically under the same credentials and onboarding process as a direct customer.
Implementation teams
Fyscaltech's own team, engaged during onboarding to provision credentials, confirm schemas, and support go-live.
This portal
The shared reference all three groups work from — one source of truth for how integration works, updated as details are confirmed.
Quick Start
If you're an external developer integrating FT AML into your own systems for the first time, this is the one section to read start to finish — everything else in this portal is reference material you'll come back to as you go.
The fastest path from zero to a working integration follows the same six steps regardless of which integration method you end up using day to day.
- Request API credentials — contact the Fyscaltech integration team (see Support) to start onboarding. They'll confirm which integration method(s) fit your use case.
- Receive sandbox access — where a sandbox/test environment applies to your chosen method, credentials and connection details are issued as part of onboarding (see Testing & Sandbox).
- Upload a sample customer — load a small test file via Bulk CSV (50–100 rows is plenty), or a single record via the Customer Ingestion API, to confirm your data format is accepted.
- Call the Screening API — submit a customer's full name to
/screenand confirm a result comes back in the response. - Check the delivery mechanism for ingestion-triggered screening — if you plan to rely on
name_screening_enabledduring ingestion instead of calling the Screening API directly, confirm with Fyscaltech how that result reaches you before building against it. - View the screening result — check the FT AML console (Alert Manager, or Name Screening) to see the same result reflected on the platform side.
If you're not sure which integration method fits your volume and timeline, read Integration Methods and the "Which one should I use?" table first — then bring that shortlist to the Fyscaltech integration team.
Platform & Architecture
FT AML sits alongside your core banking and customer-facing systems, receiving data through the integration methods in this portal and returning screening results and alerts.
Confirmed architecture detail — service topology, network/deployment diagrams, data residency options — hasn't been provided yet. What's documented today (the real request/response shape for each service) lives under Integration Methods.
Core Concepts & Data Models
A few ideas come up across all three integration methods. Understanding these up front makes the rest of this portal easier to follow.
Customer Record
The core profile of an individual or organization — the same record whether it arrives via bulk CSV or the Customer Ingestion API.
Customer Account Record
An account belonging to a customer, linked back to its Customer Record.
Customer Transaction Record
A transaction on a customer account, used by Transaction Monitoring once it's in the platform.
Ingestion
The general term for getting any of the three record types into the platform — either as a bulk CSV upload, or one at a time via the Customer Ingestion API.
Screening
Checking a customer against watchlists. Today this is done by full name, via the Screening API or triggered as part of ingestion.
Webhook
An endpoint you host, which the platform calls to deliver a screening result — only relevant if you're using the Customer Ingestion API to trigger screening rather than calling the Screening API directly.
Data models
Confirmed field names below come directly from the Customer Ingestion API and CSV bulk-upload formats — where a model isn't backed by data received from the technical team yet, that's noted rather than guessed at.
Customer
customer_id,customer_name— the only two required fields on ingestioncustomer_type—IndividualorCorporate(API) /INDIVIDUAL,CORPORATE,BUSINESS(CSV)risk_level,risk_score,pep_flag,sanctions_status— KYC/risk fields set on ingestiondate_of_birth_or_incorporation,citizenship_country,street/city— optional, improve screening match accuracy
Account
ACCOUNT_NUMBER— unique across all your accounts, not just per-customerCUSTOMER_ID— links back to the owning Customer; unmatched accounts are skippedACCOUNT_TYPE—SAVINGS,CURRENT,WALLET,FIXED_DEPOSIT,LOANACCOUNT_HOLDING_TYPE—SOLE,JOINT,TRUST,CUSTODIAL
Transaction
Alert
Case
Integration Methods
The three ways data moves between your systems and the FT AML Platform, in detail.
Which one should I use?
| Method | Best for |
|---|---|
| Bulk CSV via S3 | Initial data loads, and large-volume periodic refreshes of your customer and account population |
| Customer Ingestion API | Keeping individual customer records in sync as they're created or changed in your own system |
| Screening API | Checking one specific customer by full name at a specific moment — e.g. during onboarding |
Bulk data ingestion (CSV via S3)
For loading or refreshing data in volume, the platform reads CSV files you upload to a dedicated Amazon S3 location. Fyscaltech provisions this bucket for you during onboarding — you'll receive the bucket name and a client id (a short code, e.g. acme-bank) that scopes every path below to your own data.
Getting access
Two access options — tell your Fyscaltech contact which fits your team:
- Browser (AWS Console) — best if a person uploads files manually, a few times a month. No setup beyond a sign-in.
- Command line / automation — an access key and secret key for the AWS CLI or any S3 library, best if a scheduled job on your side produces the files without a person involved.
aws configure # one-time setup, using the access key and secret key we send you
aws s3 cp customers_20260729.csv s3://<bucket>/<client-id>/inbox/customer/
Your credentials only ever grant access to your own <client-id> folder within the shared bucket — not the whole bucket. Send data files only to the paths below; never by email or shared drive, since they contain personal data.
Where to upload
| What you're sending | Upload to |
|---|---|
| Customers | <client-id>/inbox/customer/ |
| Accounts | <client-id>/inbox/account/ |
- Name files with a sortable date —
customers_20260729.csv, not29-07-2026— since if more than one file is waiting, the platform processes whichever name sorts first, and only one file per run. - Upload customers before accounts. An account belongs to a customer, so if you're sending both at once, the customer file needs to land first — accounts referencing a customer we haven't seen yet are skipped and reported back to you.
Once a file is read, it's moved out of the inbox — an empty inbox means processing is done, not that something went wrong:
| Folder | What's in it |
|---|---|
processed/customer/, processed/account/ | Files accepted and read |
rejected/customer/, rejected/account/ | Files that couldn't be read at all — almost always a wrong or missing header row |
jobs/<run>/errors/ | Individual rows that couldn't be accepted, with the line number, column, and reason for each |
Choosing your ingestion schedule
You control when the platform looks for your files, from the back-office application — no ticket or code change needed. Set a Frequency (every day, or every N days) and one or more Run times (e.g. 02:00 and 14:00); at each of those times the inbox is checked, and an empty inbox simply means nothing runs until the next check. The schedule can also be paused while you're making changes.
Set the run time comfortably after your own export job finishes — if a nightly export finishes around 01:30, schedule 02:30 rather than 01:45.
File format rules (both files)
- CSV, UTF-8 encoded, with a header row first
- The header row must match exactly — same column names, spelling, order, all capitals. This is the one thing that gets the whole file rejected rather than individual rows.
- Every row must have the same number of columns as the header, even when values are empty (
A,,C) - Leave optional columns blank rather than writing
NULL,N/A, or-— those are treated as real values and usually rejected - Wrap a value in double quotes if it contains a comma:
"Smith, John" - Dates:
2026-07-29,2026/07/29,29-07-2026,29/07/2026, or20260729— pick one form and stay consistent - Timestamps (date + time columns):
2026-07-29T14:30:00Zis recommended (UTC) - Yes/no columns accept
true/false/1/0/Y/N/yes/no, any case - Country and currency columns use short codes (
IN,SG,AE;INR,USD) — some columns are too narrow for full names
Customer file — 52 columns
Copy this header line exactly:
CUSTOMER_ID,CUSTOMER_TYPE,CUSTOMER_NAME,STATUS_CODE,CUSTOMER_SEGMENT_CODE,CUSTOMER_SEGMENT_NAME,RISK_LEVEL,RISK_SCORE,KYC_LEVEL,KYC_LEVEL_DESCRIPTION,PEP_FLAG,SANCTIONS_STATUS,SOURCE_OF_FUNDS,KYC_AUTHORIZED_SIGNATORY_NAME,ONBOARDING_DATE,ONBOARDING_CHANNEL,STREET,CITY,PROVINCE_REGION,ADDRESS_COUNTRY,POSTAL_CODE,CITIZENSHIP_COUNTRY,RESIDENCE_OPERATION_COUNTRY,BIRTH_INCORPORATION_COUNTRY,CUSTOMER_CONTACT_NO,ACCOUNT_TYPE,ACCOUNT_OPEN_DATE,ACCOUNT_AGE_DAYS,EXPECTED_MONTHLY_SPEND,EXPECTED_MONTHLY_SPEND_CURRENCY,EXPECTED_TXN_FREQUENCY,PURPOSE_OF_ACCOUNT,FIRST_NAME,MIDDLE_NAME,LAST_NAME,GENDER,DATE_OF_BIRTH_OR_INCORPORATION,CUSTOMER_AGE,CUSTOMER_ALIASES,CUSTOMER_ID_NO,CUSTOMER_ID_TYPE,CUSTOMER_ID_EXPIRY_DATE,CUSTOMER_ID_ISSUE_DATE,CUSTOMER_ID_COUNTRY,DESIGNATION,EMPLOYER_NAME,DECLARED_INCOME,INCOME_CURRENCY,NATURE_OF_BUSINESS,COUNTRY_OF_OPERATIONS,CREATED_AT,UPDATED_AT
These six must always have a value — a row missing any of them is rejected:
| Column | What it is |
|---|---|
CUSTOMER_ID | Your own identifier, unique in the file and stable across files for the same customer (max 64 chars) — this is how an update is recognized rather than a new customer |
CUSTOMER_TYPE | One of INDIVIDUAL, CORPORATE, BUSINESS |
STATUS_CODE | Your own status, e.g. ACTIVE, DORMANT, SUSPENDED |
ONBOARDING_DATE | Date the customer was onboarded |
ONBOARDING_CHANNEL | e.g. BRANCH, MOBILE_APP, ONLINE, AGENT |
UPDATED_AT | Timestamp. Used to decide which version is newer — a record with an older UPDATED_AT will not overwrite a newer one already held |
Fixed-value columns (anything else is rejected): CUSTOMER_TYPE (INDIVIDUAL/CORPORATE/BUSINESS), RISK_LEVEL (LOW/MEDIUM/HIGH), GENDER (MALE/FEMALE/OTHER).
Four cross-field rules worth knowing, since these catch inconsistent records: an INDIVIDUAL requires FIRST_NAME and DATE_OF_BIRTH_OR_INCORPORATION; a CORPORATE requires NATURE_OF_BUSINESS; if DECLARED_INCOME is set, INCOME_CURRENCY must be too; and if both CUSTOMER_AGE and a date of birth are given, they must agree within one year.
Account file — 13 columns
ACCOUNT_NUMBER,CUSTOMER_ID,ACCOUNT_TYPE,ACCOUNT_NAME,ACCOUNT_CURRENCY_CODE,ACCOUNT_BANK_NAME,ACCOUNT_BANK_COUNTRY,ACCOUNT_STATUS,ACCOUNT_HOLDING_TYPE,SIGNATORY_CUSTOMER_ID,SIGNATORY_NAME,CREATED_AT,UPDATED_AT
Seven required: ACCOUNT_NUMBER (unique across all your accounts, not just per-customer, max 128 chars), CUSTOMER_ID (must match a customer already received, or the account is skipped), ACCOUNT_TYPE (SAVINGS/CURRENT/WALLET/FIXED_DEPOSIT/LOAN), ACCOUNT_STATUS (ACTIVE/INACTIVE/CLOSED/FROZEN), ACCOUNT_HOLDING_TYPE (SOLE/JOINT/TRUST/CUSTODIAL), CREATED_AT, and UPDATED_AT timestamps.
If ACCOUNT_HOLDING_TYPE is JOINT, TRUST, or CUSTODIAL, SIGNATORY_CUSTOMER_ID becomes required too — a shared account needs a second party. UPDATED_AT can never be earlier than CREATED_AT.
The remaining six columns are optional: ACCOUNT_NAME and ACCOUNT_BANK_NAME (free text, max 255 chars), ACCOUNT_CURRENCY_CODE and ACCOUNT_BANK_COUNTRY (short codes only — max 16 characters, so a full name like "United Arab Emirates" won't fit and is rejected; use AE instead), and SIGNATORY_CUSTOMER_ID/SIGNATORY_NAME for an additional signatory.
Common problems
| What you see | Cause | Fix |
|---|---|---|
File in rejected/, nothing loaded | Header row doesn't match exactly | Copy the header from this page exactly — check spelling, order, extra spaces, case |
Rows rejected: REQUIRED | A mandatory column is blank | Fill every required column listed above |
Rows rejected: INVALID_ENUM | A fixed-value column has something else in it | Use only the allowed values |
Rows rejected: INVALID_DATE | Date/timestamp in an unsupported form | Use one of the accepted forms; watch for DD/MM vs MM/DD mix-ups |
Rows rejected: DUPLICATE | Same CUSTOMER_ID/ACCOUNT_NUMBER twice in one file | Send each record once per file — the first occurrence is kept |
Rows rejected: MAX_LENGTH_EXCEEDED | A value is longer than the column allows | Usually a full country name in a code column, or an over-long id — see the character limits below |
Rows rejected: COLUMN_COUNT_MISMATCH | The row has more or fewer commas than the header | Usually an unquoted comma inside a value — wrap that value in double quotes |
| Accounts silently not loaded | Their customer hasn't reached us yet | Upload the customer file first; confirm that customer row wasn't itself rejected |
Customer Ingestion API
Service: datahub (Go / Gin)
Ingests or updates a single customer's information from your own system — the API equivalent of a bulk Customer Record upload, but for one record at a time and in near real time as your data changes. Ingestion is asynchronous: the endpoint validates the payload and publishes an event, then returns immediately — persistence happens moments later via a background consumer, not within the request itself.
| Method / Path | POST /datahub/ingestion/v1/customer |
| Success status | 202 Accepted |
| Headers | Content-Type: application/json |
Request payload
All fields are flat — no nesting. Only customer_id and customer_name are required; every other field is optional (omit it and it's treated as null). The field set mirrors the CSV Customer Record columns above, just in snake_case JSON:
{
"customer_id": "CUST-1001", // REQUIRED, string
"customer_name": "Jane Doe", // REQUIRED, string
"customer_type": "Individual", // "Individual" | "Corporate"; blank defaults to "Individual"
// KYC / status
"status_code": "ACTIVE",
"customer_segment_code": "RETAIL",
"risk_level": "LOW",
"risk_score": 12.5, // float
"pep_flag": false, // bool
"sanctions_status": "CLEAR",
// Onboarding
"onboarding_date": "2025-01-15", // YYYY-MM-DD or RFC3339
"onboarding_channel": "BRANCH",
// Address
"street": "...", "city": "...", "address_country": "AE",
// Personal
"first_name": "Jane", "last_name": "Doe",
"gender": "F",
"date_of_birth_or_incorporation": "1990-05-01",
// Identity document
"customer_id_no": "...", "customer_id_type": "PASSPORT",
// Screening control — omitted defaults to true
"name_screening_enabled": true // bool
}
customer_type is the only accepted enum-ish field, with no strict validation — anything other than the two accepted values is simply not recognized, blank defaults to Individual. Date fields accept YYYY-MM-DD or RFC3339; anything unparseable or empty is stored as null. name_screening_enabled omitted is treated as true — a new customer is screened by default unless you explicitly opt out.
Response
{ "code": "00000", "message": "success" }
Returned with 202 Accepted. There's no result field — nothing is persisted synchronously, so there's nothing to hand back yet.
Validation rules
- Request body is required and must be valid JSON
customer_idandcustomer_name— trimmed, must be non-emptycustomer_type— blank defaults toIndividual; any other value is accepted as-is- No format, length, or enum checks are enforced on the remaining fields
Error responses
Shape: { "code": "<code>", "message": "<message>" }
| Trigger | Code | HTTP | Message |
|---|---|---|---|
| Malformed JSON body | BR001 | 400 | invalid request payload |
| Empty body | BR001 | 400 | request body is required |
Missing customer_id | BR001 | 400 | customer_id is required |
Missing customer_name | BR001 | 400 | customer_name is required |
| Internal queue/publish failure | SWW01 | 500 | something went wrong |
Flow
POST /customer → controller (bind → validate → map) → PublishCustomerIngestionEvent publishes to SNS/SQS → returns 202. Consumers later upsert the record into Postgres and ClickHouse — neither database is written to synchronously within the request.
There is no batch HTTP ingestion endpoint. CSV/batch ingestion runs on a Temporal workflow/queue pipeline (see Bulk CSV via S3), not this API — don't loop this endpoint per record for a bulk load.
Screening API
Service: be-aml-name-screening (Python / FastAPI)
Screens a customer against watchlists and returns matches, synchronously — the result comes back in the same response. Two endpoints exist:
| Main | Back-office | |
|---|---|---|
| Method / Path | POST /riskradar/core/api/v1/screen | POST /riskradar/core/api/v1/bo/screen |
| Side effects | Creates alerts, publishes events, writes a screening ledger entry | Read-only — no alert, no publish |
| Success status | 200 | 200 |
| Headers | Content-Type: application/json | |
Use the main /screen endpoint for real onboarding/monitoring checks that should raise alerts when warranted. Use /bo/screen when you want to see what a screening result would look like — e.g. previewing a match — without it creating an alert.
A scope framework (an x-scope bitmask header) exists in the service, but is not wired to either of these two routes — sending it currently has no effect.
Request payload
/screen accepts a free-form JSON object — extra fields are allowed and preserved. customer_name is required; customer_type must be "Individual" or "Entity" (defaults to Individual); everything else is optional. The field set mirrors the Customer Ingestion API payload — status_code, risk_level, risk_score, pep_flag, address fields, personal/identity/employment fields, and so on — so a record you've already ingested can largely be re-sent as-is to screen it. Only a subset actually drives matching: customer_name, date_of_birth_or_incorporation, street, city, province_region, citizenship_country, customer_type, gender. If customer_id is omitted, a temporary TMP<uuid> is generated for that request.
{
"customer_name": "Jane Doe", // REQUIRED
"customer_id": "CUST-1001",
"customer_type": "Individual",
"gender": "F",
"date_of_birth_or_incorporation": "1990-05-01",
"citizenship_country": "IN",
"street": "...", "city": "...", "address_country": "AE"
}
/bo/screen takes the same shape, but only customer_name is accepted/required — everything else is ignored for matching purposes on this read-only endpoint.
Response envelope
{ "code": "00000", "message": "Success", "data": { /* result */ } }
Exactly three fields — code, message, data. There's no result or pagination field. Success code is 00000.
/screen result — trimmed, public
{
"data": {
"meta": { "entity_key": "...", "screening_id": "...", "timestamp": "..." },
"screening_result": { "alert_score": 87.5, "alert_category": "HIGH" }, // score 0–100
"alert_details": { "alert_code": "...", "alert_status": "..." },
"watchlist_records_matched": [ // top 10, or null
{ "UID": 12345, "full_name": "Jane Doe", "match_score": 91.2 }
]
}
}
/bo/screen result — richer, raw
Same meta/score/category fields, but alert_code and alert_status are always null (no alerting on this endpoint), plus a full raw_matches array (up to 50) with per-attribute detail — each matched attribute (name, country, date of birth, entity type, address) carries its own weight and contribution score, so you can see exactly why a match scored the way it did rather than just the final number.
Each entry in raw_matches also carries record-level detail beyond the scored attributes: match_id, watchlist_mongo_id (the underlying watchlist record's own id), risk_level, record_version, updated_date, and a soft-delete pair — is_deleted / deleted_at — showing whether the matched watchlist record has since been retired at the source.
Validation rules
/screen—customer_namerequired,customer_typemust beIndividual/Entity, numeric fields are coerced, unknown fields are allowed and preserved/bo/screen— additionally rejects an emptycustomer_namewithentity name required
Error responses
Shape: { "code": "<code>", "message": "<message>", "data": <optional> }
| Trigger | Code | HTTP | Message |
|---|---|---|---|
Bad/missing customer_name, invalid customer_type | VALIDATION_ERROR | 400 | Invalid request payload |
Empty customer_name on /bo/screen | VALIDATION_ERROR | 400 | entity name required |
| Duplicate record | CONFLICT | 409 | Duplicate record |
| Redis config-load connection failure | REDIS_CONNECTION_ERROR | 503 | as raised |
| Redis config-load operation failure | REDIS_OPERATION_ERROR | 500 | as raised |
Postgres ledger write fails (/screen only) | DATABASE_QUERY_FAILED | 500 | as raised |
| Any unmapped error | INTERNAL_SERVER_ERROR | 500 | underlying error message (str(exc)) |
Flow
POST /screen → handler validates → service loads config from Redis/Mongo → FuzzySearchEngine.screen_user matches against sanctions/watchlist data → maps to response. On /screen it also records the screening run in Postgres and, when the result scores as an alert, creates alerts and publishes events. /bo/screen skips all of that alerting — it's read-only end to end.
Authentication
Access is confirmed for one of the three integration methods so far:
- Bulk CSV via S3 — Fyscaltech provisions the S3 bucket location your files are uploaded to, along with the AWS access credentials needed to write to it. Keep these credentials on your backend or automation only, never in client-side code, and store them in a secrets manager rather than source control.
Credential/access-control setup for the Customer Ingestion API, Screening API, and any webhook delivery hasn't been confirmed yet.
Application-level request authentication (e.g. a bearer token or signed-request scheme) for the Customer Ingestion and Screening APIs is still being finalized. Until it's in place, do not assume these endpoints enforce caller identity on their own — confirm the current access-control setup for your environment (network restriction, IP allowlisting, etc.) with your Fyscaltech integration contact before relying on them being reachable only by authorized callers. This page will be updated once the mechanism is finalized.
Integration Workflows
There are two distinct request patterns depending on which APIs you use — a synchronous flow where the screening result comes back in the same response, and an asynchronous flow where it's delivered later to your webhook.
Flow A — synchronous (Screening API called directly)
(already ingested)
(full name)
in the response
Flow B — asynchronous (ingestion-triggered screening)
The Customer Ingestion API accepts a name_screening_enabled field (defaulting to true when omitted), so ingesting a customer can trigger screening as a side effect rather than requiring a separate call to the Screening API. What isn't yet confirmed in this documentation is the exact delivery mechanism for that result back to your system — likely a webhook, but confirm the specifics with your Fyscaltech integration contact before building against it.
(name_screening_enabled: true)
as a side effect
— confirm with Fyscaltech
Bulk CSV — batch (sits outside both flows)
Bulk CSV uploads are for getting Customer, Customer Account, and Customer Transaction data into the platform in volume, rather than triggering an immediate screening result back to your system. Screening or monitoring against that data happens on the platform's own schedule (see Name Screening's Batch and Delta Screening, and Transaction Monitoring's execution intervals) rather than as a direct response to the upload.
Choosing sync vs. async
| Pattern | Use it when |
|---|---|
| Synchronous (Flow A) | You need the result immediately in your own onboarding flow — e.g. blocking account opening until screening completes |
| Asynchronous (Flow B) | You're already pushing customer records via the Ingestion API and prefer not to poll — results land on your webhook as they complete |
| Batch (Bulk CSV) | You're loading or refreshing large populations, and don't need a per-record result back in real time |
Full request/response detail for both APIs is now documented under Integration Methods. Visual sequence-timing diagrams aren't available yet, but the endpoint paths, payload shapes, and response envelopes above are enough to build against.
Webhooks & Events
A webhook would only be relevant if you're using the Customer Ingestion API's name_screening_enabled flag to trigger screening as part of ingesting a customer record, rather than calling the Screening API directly. Whether delivery actually happens via webhook — versus some other mechanism — isn't confirmed yet; the pattern below is the expected shape, not a confirmed spec.
How it works (expected pattern, not yet confirmed)
- Unlike Bulk CSV, a webhook endpoint would be hosted and owned by you, not Fyscaltech
- Fyscaltech would provide the request format and signature-verification guidelines once finalized
Confirmed event names and payload structures for webhook deliveries haven't been provided yet. What is confirmed: ingestion accepts a name_screening_enabled flag (see Customer Ingestion API), so screening can run as a side effect of ingestion — the exact event/delivery format for that result is what's pending here.
This webhook is only needed for the ingest-then-screen flow through the Customer Ingestion API. If you only use bulk CSV ingestion, or call the Screening API directly for each check, you don't need to set up a webhook at all.
Errors, Limits & Best Practices
Both the Customer Ingestion API and Screening API return the business error codes below alongside their HTTP status.
Business error codes
Business-level error codes sit alongside the HTTP status and identify the specific validation or business rule that failed. Confirmed codes so far, from Customer Ingestion API and Screening API:
| Code | API | HTTP | Meaning |
|---|---|---|---|
BR001 | Customer Ingestion | 400 | Malformed body, empty body, or a required field missing |
SWW01 | Customer Ingestion | 500 | Internal queue/publish failure |
VALIDATION_ERROR | Screening | 400 | Bad or missing customer_name, invalid customer_type, or wrong field types |
CONFLICT | Screening | 409 | Mongo duplicate key |
REDIS_CONNECTION_ERROR | Screening | 503 | Redis config-load connection failure |
REDIS_OPERATION_ERROR | Screening | 500 | Redis config-load operation failure |
DATABASE_QUERY_FAILED | Screening | 500 | Postgres ledger write fails (/screen only) |
INTERNAL_SERVER_ERROR | Screening | 500 | Any unmapped internal error — the underlying message is passed through |
Both APIs share a similar response envelope shape (code + message, with Screening additionally carrying a data field) — check code first for programmatic handling, and treat message as human-readable detail rather than something to match against in logic.
Best practices
Specific guidance (idempotency behavior, retry/backoff recommendations, rate limits) for the Customer Ingestion and Screening APIs hasn't been confirmed yet. The one thing already confirmed: for bulk CSV, check the FT AML console under Data Ingestion to confirm a file was picked up and processed successfully — a successful S3 upload only means the file arrived, not that every row passed validation.
Rate limit thresholds aren't documented yet — confirm current limits with your Fyscaltech integration contact before load-testing against these endpoints.
Testing & Sandbox
For bulk CSV ingestion, the same S3 location and console feedback (Data Ingestion job status) apply regardless of whether you're testing or in production, so start with a small file to confirm your CSV format is accepted before uploading your full population.
Whether a separate sandbox/test environment exists for the Customer Ingestion and Screening APIs, and what test data it ships with, hasn't been confirmed yet.
Troubleshooting & FAQ
Common issues
| Issue | Likely cause & resolution |
|---|---|
| Bulk CSV file stuck in Processing | Check the source file for encoding or column issues; confirm in the console under Data Ingestion, and escalate to Fyscaltech if it doesn't clear |
| Screening API returns no matches for a known name | Confirm the name is being submitted exactly as registered; remember the API currently matches on full name only, not date of birth, address, or citizenship |
| Customer Ingestion call succeeds but no screening result appears | Confirm name_screening_enabled was set (it defaults to true if omitted), then check Alert Manager directly — the result-delivery mechanism back to your system isn't confirmed yet, see Webhooks & Events |
Frequently asked questions
Can we use more than one integration method at once?
Yes — most institutions use Bulk CSV for the initial customer population, then the Customer Ingestion API and/or Screening API for ongoing, real-time updates.
Do we need a webhook if we only use the Screening API?
No. Calling the Screening API directly returns the result in the same response — a webhook is only needed for the ingest-then-screen flow via the Customer Ingestion API.
Is there a sandbox we can test against before going live?
Not confirmed yet — see Testing & Sandbox.
Can the Screening API match on more than full name?
Not currently — it supports full name matching only. Additional matching fields (date of birth, address, citizenship) are available through Manual Screening in the console today.
Where do we find upcoming API changes before they land?
Keep a point of contact with the Fyscaltech integration team, and check Resources & Changelog once a public changelog is published.
Glossary
Terms and field names used throughout this Developer Portal, defined for anyone integrating for the first time.
| Term | Meaning |
|---|---|
| AML / CTF | Anti-Money Laundering / Counter-Terrorist Financing |
| KYC | Know Your Customer — the due-diligence fields carried on a Customer Record (kyc_level, kyc_level_description, kyc_authorized_signatory_name) |
| PEP | Politically Exposed Person — surfaced on a Customer Record as pep_flag |
customer_type | Whether a record is Individual or Corporate (Customer Ingestion API) / Individual or Entity (Screening API) — the two APIs use slightly different values for this field |
risk_level / risk_score | The customer's assessed AML risk, as a category (LOW/MEDIUM/HIGH) and a numeric score |
sanctions_status | Whether a customer has a known sanctions hit on file, independent of a live Screening API call |
| Watchlist | The sanctions/PEP/adverse-media data source the Screening API matches against |
| Full name screening | Matching based on the customer's full name — currently the only field the Screening API supports |
alert_score / alert_category | The Screening API's match confidence (0–100) and its banded severity (e.g. HIGH) |
entity_key / screening_id | Identifiers in a Screening API response used to trace a specific screening run |
| Bulk ingestion | Loading data via CSV file upload to S3, as opposed to the record-by-record Customer Ingestion API |
| MLRO | Money Laundering Reporting Officer — approves SAR/STR filings |
| SAR / STR | Suspicious Activity / Transaction Report filed with regulators |
Additional terms will be added here as more of the platform's API surface is confirmed.
Developer Community
A space for developers integrating with FT AML to ask questions, share patterns, and hear from each other — not just from Fyscaltech directly.
This section is a placeholder — content, links, and setup for a developer community (forum, Slack/Discord, discussion board, or similar) will be added here once provided.
Resources & Changelog
Downloadable and reference materials to speed up integration work. Anything not yet available will be published soon once received from technical team.
Postman Collection
A ready-to-import collection covering the Customer Ingestion and Screening APIs. Will be published soon once received from technical team.
PendingOpenAPI Specification
Machine-readable spec for generating clients or importing into your own API tooling. Will be published soon once received from technical team.
PendingCSV Templates
The exact Customer and Account file headers are already documented in full under Bulk CSV via S3 — copy them directly from there.
AvailableRelease Notes
A running log of platform and API changes. Will be published soon once received from technical team.
PendingSDKs
Official client libraries for common languages, planned for a future release.
FutureFyscaltech Integration Team
The most reliable resource today — see Support for contact details.
Changelog & versioning
As the Customer Ingestion and Screening APIs move from onboarding-provided specifications toward a stable published reference, changes will be communicated directly by the Fyscaltech integration team. If you're integrated against either API, keeping a point of contact with the team (see Support) is the most reliable way to hear about upcoming changes before they land.
A formal versioning policy and public changelog will be published here once the API reference itself is finalized.
Support
- In-app: Homepage > Help & Support
- Email: hello@fyscaltech.com
- Phone: +65 8080 5424
- Headquarters: Singapore · 68 Circular Road
- Account-level issues (API key provisioning, permissions) — contact your organization's Admin
What to include when you contact us
To help us resolve integration issues quickly, please include: the environment (sandbox/production), the endpoint and HTTP method, the X-Request-Id from the response headers if available, and a timestamp of when the issue occurred.
Thank you for building on FT AML.