The integration reference for engineering teams: screen names against sanctions and PEP
watchlists, sync customer profiles, receive signed screening results, and load customers, accounts and
transactions in bulk.
Ask a question about integrating with Fyscal ARCX. Answers are drawn only from this portal and link to the
exact section, for example "How do I verify the callback signature?"
Questions are stored anonymously for 90 days to improve this portal. Don't enter credentials, customer or personal data.
Get Started
Overview
Fyscal ARCX is the anti-money laundering (AML) platform from Fyscal Technologies. This portal is for the engineers who connect a bank's or fintech's systems to it: sending customer, account and transaction data in, screening names against sanctions and PEP watchlists, and receiving screening results back. Compliance analysts then work the resulting alerts and cases in the Fyscal ARCX console, which the Platform Guide documents.
For
Engineers and architects planning an integration
Reading time
About 4 min
What you can integrate: the four ways data and results move between your systems and Fyscal ARCX.
Fyscal ARCX offers four integration points. Most institutions use more than one: a bulk load for the existing customer base, then an API for each new customer.
Load or refresh customers, accounts and transactions in volume.
Transactions and accounts arrive only through bulk upload. Transaction Monitoring then runs its typologies over them on their own schedules, and the alerts appear in the console, not in an API response.
How the pieces fit
Your systemsCore banking, onboarding, CRM
Data inCustomer Ingestion API or bulk upload via S3
Screening and monitoringNames against watchlists; transactions against typologies
Result outAPI response or callback to your endpoint
Analyst decidesAlerts and cases in the Fyscal ARCX console
Diagram 1.1 — From your systems to an analyst's decision
Diagram 1.1 is the whole path. Your systems send data in. Fyscal ARCX screens customer names against the watchlists (LSEG and your own Internal Watchlist) and runs transaction monitoring rules over the transactions. When a name scores above your configured alert threshold, an alert is raised for review. Your system learns the screening outcome either in the API response or from the callback. The decision on an alert (True Match, False Match or Potential Match) is made by an analyst in the console, and a confirmed case can lead to a Suspicious Transaction Report.
AML contextA screening alert means "needs review", not "this customer is sanctioned". Most alerts are people who share a name with someone on a list. Build your onboarding flow so an alerted customer waits for the analyst's decision rather than being rejected automatically.
Who does what
Your engineering team
Fyscal
Calls the APIs from your back end, never from a browser or mobile app.
Issues the base URL and credentials for each environment.
Hosts an HTTPS endpoint for the screening result callback, and verifies its signature.
Configures your callback URL and shares the signing secret.
Uploads files to your S3 prefix in the agreed format.
Provides S3 access, the file templates and the ingestion schedule.
Handles errors, retries and duplicates as described in this portal.
Configures watchlists, match weights and alert thresholds with your compliance team.
Conventions in this portal
Base URL
ARCX_BASE_URL: the HTTPS address issued for each environment. Paths in this portal are relative to it.
Credentials
ARCX_AUTH_NAME and ARCX_AUTH_VALUE: the request header name and value issued at onboarding.
Format
JSON request and response bodies, UTF-8, over HTTPS.
Example data
Names, ids and scores in examples are fictitious.
Every code sample reads these three values from environment variables, so it runs as copied once they are set (see Set up your environment). Fyscal ARCX APIs answer with the same envelope:
code is "00000" on success. Errors use a named code such as VALIDATION_ERROR; see Errors and retries. The Customer Ingestion API answers with code and message only, because the work happens after the response.
Go from onboarding to a working screening call in five steps. You make a read-only test call first, then a real screening, then ingest one customer and watch its result arrive at your callback. Allow about 30 minutes once you have your onboarding pack.
Ask the Fyscal integration team (see Support) for Non-Production access. You need:
The base URL for Non-Production.
Your credential: the header name and value to send with every request.
If you will receive callbacks: the signing secret, and confirmation that your callback URL is registered.
If you will upload files: S3 access, your client prefix and the file templates.
Then set the three environment variables every sample in this portal uses:
export ARCX_BASE_URL="https://…" # from your onboarding pack, no trailing slash
export ARCX_AUTH_NAME="…" # the header name you were given
export ARCX_AUTH_VALUE="…" # the credential; keep it out of source control
Step 2: Make a read-only test call
POST /riskradar/core/api/v1/bo/screen runs the full matching engine but creates no alert, publishes no event and records nothing against a customer. That makes it the safe first call: it proves your network path, credential and JSON are right without leaving anything for analysts to clean up.
Expect 200 and "code": "00000". If you get something else, see Troubleshooting: a timeout usually means a network rule, and a 4xx without the JSON envelope usually means the credential didn't reach the service.
Step 3: Screen a name
Now call the production endpoint. Send your own customer_id so the screening is recorded against that customer, and add date of birth and citizenship: they improve the match score (see How matching works).
Read data.alert_details.alert_code: null means no alert was raised; a value such as ALT-NS-12102026-000042 means an analyst will review the match. The full response is described in The response.
ImportantEvery call to /screen with a name that crosses the threshold raises a real alert in the console. In Non-Production, tell your compliance contact which test names you use so their alerts can be closed.
Step 4: Ingest a customer
Send one customer through the Customer Ingestion API. The answer is 202 Accepted: the customer is stored and screened in the background.
A few seconds later, Fyscal ARCX posts the screening result for QS-TEST-002 to your callback URL, whether or not it raised an alert. A minimal receiver checks the signature, stores the result and answers 200 at once:
import hashlib, hmac, os
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["ARCX_CALLBACK_SECRET"].encode()
@app.post("/arcx/screening-result")
def screening_result():
raw = request.get_data() # raw bytes, before parsing
expected = hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("x-screening-signature", "")):
abort(401)
result = request.get_json()
save_once(result["screening_id"], result) # your code: ignore a screening_id you already have
return "", 200
const crypto = require("crypto");
const express = require("express");
const app = express();
const SECRET = process.env.ARCX_CALLBACK_SECRET;
app.post("/arcx/screening-result", express.raw({ type: "application/json" }), (req, res) => {
const expected = crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
const given = req.get("x-screening-signature") || "";
if (given.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
return res.sendStatus(401);
}
const result = JSON.parse(req.body);
saveOnce(result.screening_id, result); // your code: ignore a screening_id you already have
res.sendStatus(200);
});
When the callback arrives with "customer_id": "QS-TEST-002" and "alert_raised": false, your integration works end to end. Before going further, read Screening result callback for retries, duplicates and what to do when a callback never arrives, then work through the Go-live checklist.
Fyscal ARCX is a hosted platform: there is nothing to install. What you set up is access: a base URL and a credential for each environment, network access from your servers, a callback endpoint if you receive results, and S3 access if you upload files.
Raised and visible to your test users in the console
Raised for your compliance team to work
The two environments are separate: a credential, S3 prefix or callback secret from one never works in the other. Keep them in separate configuration so a test run can't reach Production.
Authentication
Requests pass through Fyscal ARCX's API gateway, which identifies your system from the credential header issued at onboarding. Send it on every request, from your server side only.
Fyscal tells you the header name and the value for each environment. This portal calls them ARCX_AUTH_NAME and ARCX_AUTH_VALUE.
Never put the credential in browser or mobile app code: anyone could read it there.
Store it in a secrets manager and load it at run time; don't commit it to source control.
If a credential may have leaked, contact Fyscal to replace it.
The callback works the other way round: Fyscal ARCX signs each callback with a secret shared with you, and your endpoint checks the signature (see Verify the signature).
Set up your environment
Every code sample in this portal reads three environment variables, so it runs unchanged against either environment:
Variable
Holds
Example
ARCX_BASE_URL
The HTTPS base URL for the environment, without a trailing slash
From your onboarding pack
ARCX_AUTH_NAME
The credential header name
From your onboarding pack
ARCX_AUTH_VALUE
The credential itself
Load from your secrets manager
ARCX_CALLBACK_SECRET
The callback signing secret (only if you receive callbacks)
Load from your secrets manager
# Non-Production, for example in a local .env file that is not committed
export ARCX_BASE_URL="https://…"
export ARCX_AUTH_NAME="…"
export ARCX_AUTH_VALUE="…"
export ARCX_CALLBACK_SECRET="…"
Outbound: your servers need HTTPS (port 443) access to the base URL. If your network restricts outbound traffic, ask Fyscal for the details to add to your firewall rules.
Inbound (callbacks only): a public HTTPS endpoint with a valid certificate, which accepts POST requests with a JSON body. Send its URL to Fyscal during onboarding.
TLS: use a current HTTP client and keep certificate validation switched on. Never disable verification to get past an error.
Timeouts: set a client timeout on every call (the samples use 30 seconds) so a stuck request can't hold up your own flow.
S3 access
For bulk upload, Fyscal gives you AWS access to an onboarding bucket and a client prefix: a folder that only your institution uses. Use the AWS CLI or any AWS SDK.
# One-time setup with the access issued to you
aws configure
# Check you can see your prefix ({BUCKET} and {CLIENT_ID} come from your onboarding pack)
aws s3 ls "s3://{BUCKET}/{CLIENT_ID}/inbox/"
Access Denied on that check almost always means the bucket or prefix is mistyped, or the access belongs to the other environment. The folder layout and upload process are in Bulk upload via S3.
Build Your Integration
Choosing an integration
Pick the integration by when you need the answer and how much data you move. Synchronous screening gives a result inside your own request; ingestion sends the result to your callback a few seconds later; bulk upload loads data in volume on a schedule. Most institutions combine two of them.
Diagram 4.1 starts from the data. Accounts and transactions have no per-record API, so they always go through bulk upload. For customers, the deciding question is whether your process must stop and wait for the screening outcome (for example, an account-opening screen), or can carry on and react when the callback arrives.
Diagram 4.2 — Synchronous screening with the Name Screening API
Asynchronous: ingest, then callback
Your system
Fyscal ARCX
Sends the profile to /datahub/ingestion/v1/customer
Answers 202 at once
Stores the customer
Screens the name
Posts the result to your callback
Verifies, stores, answers 200
Diagram 4.3 — Asynchronous screening through the Customer Ingestion API
Diagrams 4.2 and 4.3 show the difference that matters for your design. In the synchronous flow your request is open until the result is back. In the asynchronous flow the 202 only means "accepted": at that moment the customer has not been stored or screened yet. Your system must keep the customer's state as "screening pending" until the callback arrives.
Common combinations
Pattern
How it works
Good for
Bulk, then ingestion
Load the existing customer base by bulk upload once; send each new or changed customer through the Customer Ingestion API.
Most banks and fintechs going live with an existing book.
Screen, then ingest
Call /screen during account opening to decide on the spot; ingest the full profile once the account is opened.
Onboarding flows that must block until screening is done.
Bulk only
Upload customers, accounts and transactions on the schedule; analysts work everything in the console.
Back-office monitoring with no real-time onboarding decision.
TipIf you use "screen, then ingest", set name_screening_enabled to false on the ingestion call for customers you have just screened, so the same name isn't screened twice and doesn't raise a second alert.
Screen a person's or organisation's name against the sanctions and PEP watchlists and get the result in the same response. Use /screen in live flows: it records the screening and raises an alert when the score crosses your threshold. Use /bo/screen to test: it runs the same matching and returns the detail behind the score, but records nothing.
Send a JSON object with at least customer_name. The more identifiers you add, the better the engine can tell a namesake from a real match. The answer is 200 with the result in data.
The body is a flat JSON object. Only customer_name is required. The eight fields below are the ones the matching engine reads; other fields are accepted and ignored.
Field
Type
Description
customer_nameRequired
string
The full name of the person or organisation, as on their identity documents.
customer_id
string
Your id for the customer. Returned as meta.entity_key, and the screening is recorded against it. If you omit it, a temporary id TMP<uuid> is assigned and nothing is recorded against a customer.
customer_type
string
Individual or Entity. Defaults to Individual. Any other value is rejected with 400.
date_of_birth_or_incorporation
string
Date of birth, or date of incorporation for an entity, as YYYY-MM-DD.
gender
string
For individuals, for example M or F. Compared together with customer_type.
citizenship_country
string
Country of citizenship (or of registration for an entity). Use one convention consistently; ISO 3166 two-letter codes are recommended.
street
string
Address lines, compared with the watchlist record's address.
city
string
province_region
string
Noteaddress_country is accepted but does not change the score: the country comparison uses citizenship_country.
The response
Field
Type
Description
data.meta
entity_key
string
Your customer_id, or the temporary TMP… id if you didn't send one.
screening_id
string
This screening's reference, SCR-NS-DDMMYYYY-NNNNNN. Quote it to support.
timestamp
string
When the screening ran, in UTC, ISO 8601 without an offset (for example 2026-10-12T09:41:07.512844).
rules_version
string
Version of the matching rules.
config_version
string
Version of the match-weight configuration used. Useful when comparing results before and after a configuration change.
data.screening_result
alert_score
number
The best match's score, 0 to 100. 0 when nothing matched.
alert_category
string
The band the score falls in. Band names are set in your deployment's alert configuration (for example High Risk); NO_MATCH when nothing matched.
data.alert_details
alert_code
string | null
The alert raised for analysts, ALT-NS-DDMMYYYY-NNNNNN, or null if no alert was raised. This is the field to decide on.
alert_status
string | null
open when an alert was raised, otherwise null.
data.watchlist_records_matched
[ ]
array | null
Up to 10 matched watchlist records, strongest first, or null when nothing matched.
[ ].UID
integer
The watchlist record's id, as shown in the console's Watchlist Search.
[ ].full_name
string
The name on the watchlist record.
[ ].match_score
number
That record's score, 0 to 100.
A name with no match returns "alert_score": 0.0, "alert_category": "NO_MATCH", both alert_details fields null, and "watchlist_records_matched": null.
How matching works
Matching runs in two stages. First, watchlist records whose names are close to customer_name are retrieved, allowing for spelling differences: "Victor" finds "Viktor". Candidates whose names are too far apart are dropped. Then each remaining candidate is scored on up to five components, and the weighted result becomes its 0–100 score.
Find candidatesBy name only, spelling-tolerant
Score componentsName, date of birth, address, country, type and gender
Weight and combineWeights set per deployment
Compare to thresholdAbove the "No Alert" band: alert raised
Diagram 5.1 — From a name to an alert decision
Component
Your fields
How it compares
Name
customer_name
Fuzzy text similarity combined with a sound-alike (phonetic) comparison, so transliterations still match.
Date of birth
date_of_birth_or_incorporation
Year, month and day compared separately.
Address
street, city, province_region
Each line compared with the record's address.
Country
citizenship_country
Compared with the record's country.
Type and gender
customer_type, gender
Compared with whether the record is an entity, a man or a woman.
A component you don't send, or that the watchlist record doesn't have, is left out, and the remaining weights are scaled up to fill its share. So a name-only request is scored on the name alone: it works, but it can't tell two people with the same name apart. The weights and the alert bands are configured for your deployment with your compliance team; analysts see the same breakdown on each alert in the console.
AML contextSending date of birth and citizenship is the most effective way to reduce false positives. Common names can otherwise reach a high score on the name alone, and every such alert costs an analyst review.
/bo/screen takes the same fields as /screen and runs the same matching, but raises no alert and records nothing against a customer. It returns more detail: every candidate (up to 50) with the score and weight of each component. Use it to check connectivity, to see why a name scores as it does, and to test before go-live. Don't use it in a live flow: no alert reaches your analysts.
The example is shortened. In raw_matches, each component's weight is its share of the score in percent, after the scaling described above, so the weights in one match add up to 100: here 60 × 91.0 + 20 × 100 + 20 × 80.0, divided by 100, gives 90.6. alert_code and alert_status are always null here.
Acting on the result
alert_details.alert_code
Meaning
What your system should do
null
Nothing crossed the alert threshold.
Continue onboarding.
ALT-NS-…
A possible match is waiting for an analyst.
Hold the customer as "pending compliance review" and store the alert_code. Don't reject automatically, and don't tell the customer they matched a sanctions list.
Decide on alert_code, not on alert_score or alert_category. The threshold and band names belong to your compliance configuration and can change; the presence of an alert always reflects the alert actually raised.
Important/screen is not idempotent. Each call creates a new screening_id and, for a matching name, a new alert. Don't retry a call that succeeded, and don't call it on every page load of your onboarding form: call it once when the application is submitted.
Send one customer profile at a time, as it is created or changed in your core system. Fyscal ARCX stores the profile, screens the name unless you switch screening off, and posts the result to your callback. The call answers 202 Accepted straight away; everything else happens in the background.
202 means "accepted for processing". There is no data: the profile has not been stored or screened yet when you receive it. Treat any other status as "not accepted" and see Customer Ingestion API errors.
What happens after the 202
Accepted202 returned to you
StoredThe profile becomes the customer's record, shown in Customer 360
ScreenedIf name_screening_enabled is not false
Alert raisedOnly if the score crosses the threshold
Callback sentTo your endpoint, alert or not
Diagram 6.1 — An ingested customer, from the 202 to your callback
Diagram 6.1 normally completes in seconds. The screening reads the stored profile, using the same fields and matching as the Name Screening API, and the result reaches you as described in Screening result callback. Analysts see any alert in the console with the full profile attached.
Request fields
The body is one flat JSON object; there are no nested objects. Send every field you hold. Fields marked Screening are used by the matching engine; the rest describe the customer for analysts and monitoring.
Field
Type
Description
Identity
customer_idRequired
string, ≤ 64
Your unique id for the customer. Sending the same id again updates that customer. Echoed back in the callback.
customer_nameRequired
string, ≤ 128
Full name or registered name. Screening.
customer_type
string
Individual or Entity; Individual if blank. Screening.
first_name, middle_name, last_name
string
Name parts, for display.
customer_aliases
string
Other names the customer is known by.
gender
string, ≤ 16
For example M or F. Screening.
date_of_birth_or_incorporation
date
YYYY-MM-DD. Screening.
customer_age
integer
Age in years.
Identity document
customer_id_no
string
Document number.
customer_id_type
string
Document type, for example PASSPORT.
customer_id_issue_date, customer_id_expiry_date
date
YYYY-MM-DD.
customer_id_country
string
Issuing country.
Address and nationality
street, city, province_region
string
Address lines. Screening.
postal_code
string, ≤ 16
Postal code.
address_country
string
Country of the address.
citizenship_country
string
Citizenship or country of registration. Screening.
residence_operation_country
string
Country of residence or operation.
birth_incorporation_country
string
Country of birth or incorporation.
customer_contact_no
string, ≤ 16
Phone number, for example +971501234567.
KYC and risk
status_code
string
Customer status in your system, for example ACTIVE.
risk_level
string
Your risk rating, for example LOW, MEDIUM or HIGH.
risk_score
number
Your numeric risk score, up to two decimals.
kyc_level, kyc_level_description
string
KYC level and what it means, for example FULL and "Verified national ID and proof of address".
kyc_authorized_signatory_name
string
Authorised signatory, for entities.
pep_flag
boolean
true if your KYC identified the customer as a politically exposed person.
Whether to screen this customer and send a callback. true if omitted. See Controlling screening.
TipUse one convention for countries across every field and every integration, and keep it the same as in your bulk files. ISO 3166 two-letter codes (IN, AE, SG) are recommended; mixing "India" and "IN" weakens country matching and makes filtering in the console unreliable.
Controlling screening
name_screening_enabled
Stored
Screened
Callback
true
Yes
Yes
Yes, whatever the outcome
omitted
Yes
Yes
Yes
false
Yes
No
No
Send false when the change doesn't affect screening (for example a new phone number), or when you have just screened the name with /screen. With false, no callback will ever arrive for that request, so your system must not wait for one.
Updates and repeats
Updates: send the customer again with the same customer_id; the record is stored keyed on it, so the existing customer is updated rather than duplicated. Send the complete current profile each time, not only the changed fields.
Repeated requests: a customer is screened at most once in any 5-minute window. If you send the same customer_id twice within 5 minutes, it is stored both times but screened, and called back, once. A genuine profile change after that window is screened again.
Retries: if a request fails with a network error or a 5xx, retrying it is safe: the record is keyed on customer_id, so it is not duplicated. See Retries.
When a customer sent through the Customer Ingestion API has been screened, Fyscal ARCX posts the result to an HTTPS endpoint you host. It arrives for clear results as well as alerts, usually within seconds. This chapter covers the payload, how to check that it came from Fyscal, and how to handle retries, duplicates and missed deliveries.
POST /datahub/ingestion/v1/customer with name_screening_enabledtrue or omitted
Yes, one per screening, alert or not
POST /datahub/ingestion/v1/customer with name_screening_enabled: false
No
POST /riskradar/core/api/v1/screen or /bo/screen
No: the result is in the response
Bulk upload via S3
No: results are worked in the console
Fyscal configures one callback URL and one signing secret for your deployment during onboarding. Before you test, confirm with your Fyscal contact that delivery is switched on in the environment you are using.
content-type: application/json
x-screening-signature: 5f0c…e91a # hex HMAC-SHA256 of the raw body
Payload fields
Field
Type
Description
customer_id
string
Your id, exactly as you sent it. Use it to find the customer in your system.
screening_id
string
This screening's reference, SCR-NS-DDMMYYYY-NNNNNN. Unique per screening: use it to discard duplicates.
screened_at
string
When the screening ran, in UTC, ISO 8601 without an offset.
alert_raised
boolean
The decision field.false: nothing crossed the threshold. true: an analyst must review alert_code.
alert_code
string | null
The alert analysts work from, ALT-NS-DDMMYYYY-NNNNNN; null when alert_raised is false.
score
number
The strongest match's score, 0 to 100.
category
string
The band the score fell in, as named in your alert configuration, or NO_MATCH.
matches
array
Up to 10 matches, strongest first; empty when nothing matched.
matches[].matched_name
string
The name on the watchlist record.
matches[].score
number
That record's score.
matches[].risk_level
string
The band for that record's score.
matches[].category
string
The record's list category, for example SANCTION.
matches[].source
string
The list the record comes from, for example OFAC SDN.
AML contextalert_raised: true means "needs review", not "confirmed match". Keep the customer pending until your compliance team decides, and never reveal to the customer that they were flagged: tipping off a customer about a suspicion is an offence in most jurisdictions.
Verify the signature
Your callback URL is on the public internet, so anyone who finds it could post a fake "no alert" result. Each genuine callback carries x-screening-signature: the hex-encoded HMAC-SHA256 of the raw request body, keyed with the signing secret Fyscal shared with you. Recompute it and compare before you trust the body.
Hash the raw bytes, before parsing. Parsing and re-serialising JSON changes spacing and key order, and the signature will never match. In Express use express.raw(); in Flask, request.get_data().
Compare in constant time (hmac.compare_digest, crypto.timingSafeEqual, hmac.Equal), never with ==.
Reject a mismatch with 401 and log it; don't process the body.
Signature checker
Paste a body and your test secret to see the signature your code should produce. It is computed in your browser and never sent anywhere. Use a Non-Production secret only.
Expected signature…
NoteThe signature covers the body only; there is no timestamp. A captured genuine callback would still verify if someone resent it later. Discarding screening_ids you have already processed (below) protects you from that, and the alert's current status in Fyscal ARCX is always the authority, not an old callback.
Delivery, retries and duplicates
Setting
Value
What it means for you
Success
Any 2xx
Answer 200 as soon as the request is verified and stored; do the slow work afterwards.
Timeout
10 seconds
A slower answer counts as a failure and is retried.
Attempts
3 in total (the first try and 2 retries)
The same screening_id can arrive more than once.
Wait between attempts
2 seconds
A restart of a few seconds on your side can still lose a callback.
After the last attempt
Dropped
The result is not sent again. See below.
Delivery is best effort. If your endpoint is down for longer than the retry window, that callback is lost; the screening and any alert still exist in Fyscal ARCX. Design for it:
Keep every ingested customer in a "screening pending" state until its callback arrives.
Run a regular job that lists customers still pending after, say, 15 minutes, and raise them with your compliance team, who can see each customer's screening history and alerts in the console.
Don't re-ingest a pending customer just to get a callback: it is stored again, and screened again once 5 minutes have passed, which can raise a second alert.
Build a reliable receiver
Read raw bodyBefore any JSON parsing
Verify signatureMismatch: answer 401
Seen this screening_id?Yes: answer 200, do nothing
Store the resultIn one transaction with the screening_id
Answer 200Then update onboarding, notify, etc.
Diagram 7.1 — Handling one callback
Diagram 7.1 is the order that keeps a receiver safe and fast. The duplicate check comes after the signature check, so a forged request can't mark a real screening_id as seen. Storing the result before answering means a crash after the 200 never loses it. Everything slow, such as notifying an onboarding team, happens after the answer, so you stay well inside the 10-second timeout.
Load customers, accounts and transactions in volume by uploading CSV files to your folder in Fyscal ARCX's onboarding bucket. Files are picked up on a schedule, up to three times a day, validated row by row, and loaded. It is the only way to send accounts and transactions, and the usual way to load an existing customer base before go-live.
Customers loadedValidated, loaded, and their names screened
Accounts and transactionsLoaded once the customer load completes
Job recordedCounts, status and skipped rows in the console
Diagram 8.1 — One scheduled bulk load
Diagram 8.1 shows the order that matters. Uploading a file doesn't start processing: the next scheduled run does. Each run loads customers first, and only then starts the account and transaction loads, so accounts and transactions can refer to customers in the same run. Loaded customers are screened against the watchlists just like customers sent through the API; any alerts appear in the console, and no callback is sent.
TipYour administrators see and change the run times in the console, under Data Ingestion → Batch Ingestion → Change Schedule (see the Platform Guide). Upload well before a run time, and agree the schedule with them if your files arrive at a fixed hour.
Folder layout
Fyscal gives you a bucket ({BUCKET}) and a client prefix ({CLIENT_ID}) for each environment. Upload only into the three inbox folders; Fyscal ARCX writes the others.
s3://{BUCKET}/{CLIENT_ID}/
├── inbox/
│ ├── customer/ ← upload customer files here
│ ├── account/ ← upload account files here
│ └── transaction/ ← upload transaction files here
├── processed/ files that have been loaded, by type
├── rejected/ files that could not be loaded, by type
└── jobs/ working files for each run, with row errors
After a run, look for each file under processed or rejected and its type, for example processed/customer/. An empty inbox at run time is normal: that run is recorded as No Files Found.
Upload files
# Upload today's files (names are examples)
aws s3 cp customers_20261012.csv "s3://{BUCKET}/{CLIENT_ID}/inbox/customer/"
aws s3 cp accounts_20261012.csv "s3://{BUCKET}/{CLIENT_ID}/inbox/account/"
aws s3 cp transactions_20261012.csv "s3://{BUCKET}/{CLIENT_ID}/inbox/transaction/"
# After the run: what was loaded and what was rejected
aws s3 ls "s3://{BUCKET}/{CLIENT_ID}/processed/customer/"
aws s3 ls "s3://{BUCKET}/{CLIENT_ID}/rejected/customer/"
import boto3
s3 = boto3.client("s3")
BUCKET, CLIENT = "{BUCKET}", "{CLIENT_ID}" # from your onboarding pack
for local, kind in [("customers_20261012.csv", "customer"),
("accounts_20261012.csv", "account"),
("transactions_20261012.csv", "transaction")]:
s3.upload_file(local, BUCKET, f"{CLIENT}/inbox/{kind}/{local}")
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { readFile } from "node:fs/promises";
const s3 = new S3Client({});
const BUCKET = "{BUCKET}", CLIENT = "{CLIENT_ID}"; // from your onboarding pack
for (const [file, kind] of [["customers_20261012.csv", "customer"],
["accounts_20261012.csv", "account"],
["transactions_20261012.csv", "transaction"]]) {
await s3.send(new PutObjectCommand({
Bucket: BUCKET, Key: `${CLIENT}/inbox/${kind}/${file}`, Body: await readFile(file)
}));
}
Upload complete files only. Write to a temporary name and rename, or upload in one call, so a run never picks up half a file.
Put a date or sequence in each file name so you can match files to jobs later.
Upload customer files for new customers in the same run as, or before, their accounts and transactions.
File format
Files are CSV with a header row. Fyscal provides the template for each file type at onboarding: the exact column names, which are required, and the date and number formats. Copy the header row from the template rather than typing it.
Amount, currency, time, type and channel, with the sender and receiver of each transaction. These are the fields Transaction Monitoring typologies run on.
AML contextTransaction Monitoring can only find what your files contain. A missing sender or receiver country, channel or customer link silently weakens rules such as high-risk-country or structuring typologies. Fill every field you hold, and keep codes (countries, currencies, channels) consistent from file to file.
Check the result
Every run creates a job per file type. Your administrators see the jobs in the console under Data Ingestion → Batch Ingestion, with Total Records, Ingested and Skipped Rows counts, and can download the source file and an error-rows CSV of the rows that were skipped.
Status
Meaning
Init, Validating, Validated, Published, Ingesting
The job is in progress.
Completed
The job finished. It can still have skipped rows: check the Skipped Rows count.
Partial Success
Not every row loaded: check Skipped Rows.
Failed
The job didn't complete.
No Valid Record
The file had no row that could be loaded.
No Files Found
The inbox folder was empty at run time.
To fix skipped rows, download the error-rows CSV, correct those rows in your source system, and upload them in a new file. Don't re-upload the whole original file unless your compliance team agrees: loaded customers are screened again.
You don't upload watchlists to S3. Fyscal loads the LSEG sanctions and PEP data. Your compliance team maintains your own Internal Watchlist by uploading a CSV in the console, under Data Ingestion → Watchlist Ingestion → Upload Internal Watchlist, where a sample file can be downloaded. Both lists are used by every screening, through the API or bulk upload.
How Fyscal ARCX reports a failed request, which errors your code should fix and which it can retry, and how to diagnose the problems integrators meet most often.
Branch on the HTTP status and code, never on message: message text can change.
Log message and data for your own troubleshooting, but don't show them to end users.
A response that is not JSON, or has no code, didn't come from the Fyscal ARCX service itself (for example a gateway or network error). Treat it by its HTTP status.
Customer Ingestion API errors
Only 202 means the customer was accepted. Any other status means it was not, and nothing will be stored, screened or called back for that request.
Status
Meaning
What to do
4xx
The request was rejected, for example because the body isn't valid JSON or a required field is missing.
Retry with care: if the first call did reach the service, a second call creates a second screening and possibly a second alert
A reasonable policy is up to three retries, waiting about 1, 2 and then 4 seconds with a little random jitter, and then failing the operation into your own error queue for a person to look at. Never retry in a tight loop.
import random
import time
import requests
def post_with_retry(url, headers, payload, attempts=4):
for attempt in range(attempts):
try:
response = requests.post(url, headers=headers, json=payload, timeout=30)
except (requests.ConnectionError, requests.Timeout):
response = None
if response is not None and response.status_code < 500:
return response # success, or a 4xx to fix (don't retry)
if attempt == attempts - 1:
raise RuntimeError("Fyscal ARCX unavailable after retries")
time.sleep(2 ** attempt + random.uniform(0, 0.5)) # 1 s, 2 s, 4 s, plus jitter
async function postWithRetry(url, headers, payload, attempts = 4) {
for (let attempt = 0; attempt < attempts; attempt++) {
let response = null;
try {
response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json", ...headers },
body: JSON.stringify(payload),
signal: AbortSignal.timeout(30_000)
});
} catch { /* network error or timeout: retry */ }
if (response && response.status < 500) return response; // success, or a 4xx to fix
if (attempt === attempts - 1) throw new Error("Fyscal ARCX unavailable after retries");
await new Promise(r => setTimeout(r, 2 ** attempt * 1000 + Math.random() * 500));
}
}
Troubleshooting
Symptom
Likely cause
Check
Connection timeout on every call
Outbound HTTPS blocked by a firewall or proxy
From the same server, curl -v "$ARCX_BASE_URL" and see where it stops. Ask your network team to allow it.
401 or 403 without the Fyscal ARCX envelope
The credential is missing, wrong, or for the other environment
Print the header name (not the value) your code sends; compare with your onboarding pack for that environment.
422 with "detail"
The body isn't a JSON object, or Content-Type isn't application/json
Send a JSON object with the header set; most HTTP libraries do this when you pass a dictionary or object as JSON.
A known listed name returns no match
Spelling far from the listed name, or the score is below the alert threshold
Call /bo/screen with the same body and read raw_matches: it shows every candidate and its component scores.
name_screening_enabled was false; or a repeat within 5 minutes; or your endpoint failed all three attempts; or delivery isn't switched on in this environment
Check the request body, your endpoint's access log for POSTs, and confirm delivery with Fyscal.
Callback signature never matches
The body was parsed before hashing, or the secret is from the other environment
Hash the raw bytes; try the signature checker with the exact body you logged.
Access Denied on S3
Wrong bucket or prefix, or access for the other environment
aws s3 ls "s3://{BUCKET}/{CLIENT_ID}/inbox/" with the values from your onboarding pack.
Bulk job shows No Files Found
Files were uploaded to the wrong folder, or after the run time
List inbox/customer/, inbox/account/ and inbox/transaction/; check the schedule with your administrators.
When you contact support, include the environment, the endpoint and method, the time of the request in UTC, the HTTP status and response body, and any screening_id, alert_code or customer_id involved. Never send a credential or a signing secret.
Work through this chapter in Non-Production before you ask for Production access. The tests prove each path end to end, including the cases integrators most often get wrong; the security list covers what reviewers will ask about.
Agree the test names with your compliance contact first, so they can recognise and close the alerts these tests raise.
#
Test
Expected result
1
/bo/screen with any name
200, "code": "00000", no alert in the console.
2
/screen with a name that won't match
alert_codenull; your flow continues onboarding.
3
/screen with a name your compliance contact gives you that matches a list entry
alert_code set; your flow holds the customer; the alert appears in the console.
4
Ingest a customer with a name that won't match
202, then a callback with "alert_raised": false and "matches": [].
5
Ingest a customer with a matching name
A callback with "alert_raised": true and an alert_code.
6
Ingest a customer with "name_screening_enabled": false
202 and no callback. Your flow must not wait for one. This is the test most often failed.
7
Send test 4's request twice within 5 minutes
One callback, not two.
8
Post a callback body to your endpoint with a wrong signature
Your endpoint answers 401 and stores nothing.
9
Replay a genuine callback you already processed
Your endpoint answers 200 and changes nothing.
10
Stop your callback endpoint, ingest a customer, start it again
Your reconciliation job reports the customer as still pending.
11
Upload a small customer, account and transaction file (50–100 rows)
Jobs in the console with the expected counts; the files under processed/.
12
Upload a file with a few deliberately bad rows
Skipped Rows counts them; the error-rows CSV lists them.
Integration checklist
Every call sets a client timeout, and 4xx and 5xx are handled differently (see Retries).
Onboarding decisions use alert_code (API) or alert_raised (callback), never the score or category.
An alerted customer is held for review, not rejected automatically, and is never told why.
/screen is called once per application, not on every form change.
Ingested customers are tracked as "screening pending" until the callback arrives, with a job that reports any still pending.
The callback receiver verifies the signature on the raw body, discards repeated screening_ids and answers within 10 seconds.
Date of birth, citizenship and address are sent wherever you hold them.
Country, currency and code values are consistent across the API and the bulk files.
Bulk files are uploaded complete, into the right inbox folder, before the scheduled run time.
Someone owns checking the Batch Ingestion jobs and their skipped rows after each run.
Security checklist
Credentials, the callback secret and AWS keys are in a secrets manager, separate for each environment, and never in source control, logs or client-side code.
All API calls are made from your servers over HTTPS with certificate validation on.
The callback endpoint accepts only POST, rejects bad signatures, and is served over HTTPS with a valid certificate.
AWS access is limited to the people and jobs that upload files.
Logs keep identifiers (customer_id, screening_id, alert_code) but not full customer profiles.
Test data in Non-Production is fictitious: no real customer data.
You know how to ask Fyscal to replace a credential or secret, and who does it on your side.
Moving to Production
Tests passAll twelve, in Non-Production
Production accessBase URL, credential, callback secret, S3 prefix
ConfigurationProduction values in your secrets manager
Initial loadExisting customers by bulk upload
Switch onLive calls and callbacks; watch the first days closely
Diagram 10.1 — From Non-Production to Production
Diagram 10.1 is the usual sequence. Plan the initial bulk load with your compliance team: every loaded customer is screened, so a large first load can create a large first batch of alerts for analysts to work.
Yes, and most institutions do: bulk upload for the existing customer base, then the Customer Ingestion API or the Name Screening API for new customers. See Common combinations.
Do we need a callback endpoint if we only call /screen?
No. /screen returns the result in its response. Callbacks are only sent for customers ingested through the Customer Ingestion API.
Is there an SDK?
No. The APIs are plain JSON over HTTPS, so any HTTP client works; this portal shows cURL, Python, Node.js and Go. Use the Build a request buttons in the API chapters to generate the code for your fields.
Can we test without creating alerts?
Yes: /bo/screen runs the same matching and returns the full scoring detail, but raises no alert and records nothing. See Test without raising alerts.
Which fields affect the screening score?
Eight: customer_name, date_of_birth_or_incorporation, street, city, province_region, citizenship_country, customer_type and gender. Candidates are found by name, then scored on all the components you send. See How matching works.
Why does the same name score differently in two calls?
Because the other fields differ, or the watchlist or match configuration changed in between. Compare config_version in the two responses, and use /bo/screen to see the component scores.
What does alert_category contain?
The name of the band the score falls in, as configured for your deployment (for example "High Risk"), or NO_MATCH. Don't hard-code band names: decide on alert_code or alert_raised.
Is the Customer Ingestion API synchronous?
No. It answers 202 as soon as the request is accepted; the customer is stored and screened afterwards, and the result reaches your callback.
What if our callback endpoint is down?
Each callback is tried three times, 2 seconds apart, and then dropped. The screening and any alert are still in Fyscal ARCX. Track pending customers and reconcile; see Delivery, retries and duplicates.
ISO 8601 in UTC, without an offset, for example 2026-10-12T09:41:07.512844. Parse them as UTC.
Where are suspicious transaction reports filed?
In the console, by your compliance team. STRs are prepared from closed cases in Case Manager, in the GoTRACS format; there is no API for them. See the Platform Guide.
Glossary
Term
Meaning
202 Accepted
An HTTP status meaning the request was accepted for processing later; the work is not done yet.
Alert
A possible match raised for an analyst to review in the console. Identified by alert_code.
Alert band
A score range with a name (for example "High Risk") set in the deployment's alert configuration. The band below the alert threshold is "No Alert".
AML
Anti-money laundering: the controls that detect and report money laundering and terrorist financing.
Backoff
Waiting longer between each retry of a failed request, so a struggling service can recover.
Bulk upload
Loading customers, accounts and transactions as CSV files through S3, processed on a schedule.
Callback (webhook)
An HTTPS request Fyscal ARCX sends to your endpoint with a screening result.
Candidate
A watchlist record found for a name and then scored against the customer.
Client prefix
Your institution's folder in the onboarding S3 bucket, {CLIENT_ID}.
Customer 360
The console's single view of a customer: profile, accounts, transactions and screening history.
Entity
A customer that is an organisation, not a person. Sent as customer_typeEntity.
False positive
An alert for a customer who is not the person or organisation on the list, usually because of a shared name.
HMAC-SHA256
A keyed hash used to sign callbacks, proving they came from Fyscal ARCX and weren't altered.
Idempotent
Safe to repeat: doing it twice has the same effect as once. Ingestion is idempotent on customer_id; /screen is not.
Internal Watchlist
Your institution's own list of names to screen against, maintained in the console.
KYC
Know your customer: identifying and verifying customers and assessing their risk.
LSEG
The provider of the sanctions and PEP data Fyscal loads into the watchlist.
Non-Production
The environment for building and testing an integration, with test data only.
PEP
Politically exposed person: someone with a prominent public function, or their family and close associates, who needs enhanced due diligence.
Phonetic matching
Comparing how names sound, so transliterated spellings ("Mohammed", "Muhammad") can still match.
Sanctions list
A government or international list of people and organisations that institutions must not deal with.
Screening
Comparing a customer's name and identifiers with watchlist records.
STR
Suspicious transaction report: the report filed with the financial intelligence unit when activity is suspected of money laundering.
Threshold
The score above which an alert is raised: the top of the "No Alert" band.
Tipping off
Telling a customer they are suspected or under review. Prohibited in most jurisdictions.
Transaction Monitoring
Running typologies (rules) over transactions to find patterns such as structuring.
Typology
A monitoring rule describing a money-laundering pattern.
UID
A watchlist record's id, as shown in the console.
Watchlist
The combined sanctions, PEP and internal lists that names are screened against.
Identifier formats
Identifier
Format
Example
Where you see it
screening_id
SCR-NS-DDMMYYYY-NNNNNN
SCR-NS-12102026-000187
/screen response, callback
alert_code
ALT-NS-DDMMYYYY-NNNNNN
ALT-NS-12102026-000042
/screen response, callback, console
entity_key
Your customer_id, or TMP + a UUID
CUST-1001
/screen response
The date part is the UTC date of the screening, and the six-digit number restarts each day.
Include the environment, endpoint, time (UTC), HTTP status, response body and any screening_id, alert_code or customer_id. Never send credentials, secrets or real customer data by email.
Changes to the APIs and to this portal, newest first. Each entry links to the chapter it affects.
For
Everyone
Reading time
About 1 min
2026.1012 Oct 2026
New
Developer Portal rebuilt and verified
Every endpoint, field, error code and callback detail checked against the platform's source. Adds the request builder, the callback signature checker, matching explained, a go-live test plan, and Ask AI. Removes content that did not match the platform. See Overview.