curl -X POST https://app.instantcompliance.ai/api/v1/customers \
-H "Authorization: Bearer ic_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"externalId": "crm-7741",
"fullName": "Jane Doe",
"email": "jane@example.com"
}'await fetch('https://app.instantcompliance.ai/api/v1/customers', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.IC_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID()
},
body: JSON.stringify({
externalId: 'crm-7741',
fullName: 'Jane Doe',
email: 'jane@example.com'
})
});
import os, uuid, requests
requests.post(
'https://app.instantcompliance.ai/api/v1/customers',
headers={
'Authorization': f"Bearer {os.environ['IC_API_KEY']}",
'Idempotency-Key': str(uuid.uuid4())
},
json={
'externalId': 'crm-7741',
'fullName': 'Jane Doe',
'email': 'jane@example.com'
}
)
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.instantcompliance.ai/api/v1/customers",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'externalId' => 'crm-7741',
'fullName' => 'Jane Doe',
'email' => 'jane@example.com'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.instantcompliance.ai/api/v1/customers"
payload := strings.NewReader("{\n \"externalId\": \"crm-7741\",\n \"fullName\": \"Jane Doe\",\n \"email\": \"jane@example.com\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.instantcompliance.ai/api/v1/customers")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"externalId\": \"crm-7741\",\n \"fullName\": \"Jane Doe\",\n \"email\": \"jane@example.com\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.instantcompliance.ai/api/v1/customers")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"externalId\": \"crm-7741\",\n \"fullName\": \"Jane Doe\",\n \"email\": \"jane@example.com\"\n}"
response = http.request(request)
puts response.read_body{
"id": "550e8400-e29b-41d4-a716-446655440000",
"external_id": "crm-7741",
"type": "INDIVIDUAL",
"full_name": "Jane Doe",
"email": "jane@example.com",
"kyc_status": "VERIFIED",
"kyc_started_at": "2026-06-23T01:00:00Z",
"kyc_completed_at": "2026-06-23T01:12:00Z",
"identity": {
"verified_legal_name": "JANE DOE",
"verified_country": "AUS"
},
"aml": {
"status": "CLEAR",
"screened_at": "2026-06-23T01:12:00Z",
"last_reviewed_at": null,
"flags": {
"pep": false,
"sanctions": false,
"adverse_media": false,
"terrorism": false
}
},
"added_via": "INTEGRATION",
"created_at": "2026-06-22T22:14:00Z",
"updated_at": "2026-06-23T01:12:05Z"
}{
"id": "550e8400-e29b-41d4-a716-446655440000",
"external_id": "crm-7741",
"type": "INDIVIDUAL",
"full_name": "Jane Doe",
"email": "jane@example.com",
"kyc_status": "VERIFIED",
"kyc_started_at": "2026-06-23T01:00:00Z",
"kyc_completed_at": "2026-06-23T01:12:00Z",
"identity": {
"verified_legal_name": "JANE DOE",
"verified_country": "AUS"
},
"aml": {
"status": "CLEAR",
"screened_at": "2026-06-23T01:12:00Z",
"last_reviewed_at": null,
"flags": {
"pep": false,
"sanctions": false,
"adverse_media": false,
"terrorism": false
}
},
"added_via": "INTEGRATION",
"created_at": "2026-06-22T22:14:00Z",
"updated_at": "2026-06-23T01:12:05Z"
}{
"error": {
"code": "unauthorized",
"message": "Invalid or revoked API key."
}
}{
"error": {
"code": "forbidden_scope",
"message": "This API key does not have the required scope. customers:write",
"details": {
"required_scope": "customers:write"
}
}
}{
"error": {
"code": "unauthorized",
"message": "<string>",
"details": {}
}
}{
"error": {
"code": "validation_failed",
"message": "Invalid customer payload.",
"details": {
"issues": {
"email": [
"Must be a valid email address."
]
}
}
}
}{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded. Slow down and retry shortly.",
"details": {
"limit": 60,
"window_seconds": 60
}
}
}Create or upsert a customer
Ingest an individual customer. The record is created with
added_via = INTEGRATION, and kyc_status is derived by the same
AML/CTF rule the in-app onboarding uses: providing a designated
service makes customer due diligence required (NOT_STARTED — i.e.
KYC is owed but not begun), otherwise the record lands
NOT_REQUIRED. Ingesting never triggers KYC and never charges —
start verification when you’re ready with
POST /customers/{id}/kyc (or from inside Instant Compliance).
Sole traders
With type: SOLE_TRADER you can also pass the individual’s
registered businessName and abn. When an ABN is supplied the
server runs its own ABR (Australian Business Register) lookup and, on
an active match, marks the business as register-verified on the
record — best-effort: an ABR miss or outage never blocks ingest, the
back office can re-verify in-app.
Existing (pre-commencement) clients
With risk.isNewCustomer: false you can supply clientSinceDate,
the s36(4) preCommencementAssessment, and — on the “no new
services” branch — preExistingServices. A recorded no-trigger
assessment keeps the client on the monitoring-only carve-out
(kyc_status = NOT_REQUIRED with services recorded); without an
assessment the conservative rule applies (any designated service ⇒
CDD required). These are exactly the rules the in-app onboarding
wizard applies — same shared engine.
Designated services
Pass designatedServices to record the specific designated
service(s) you provide to this customer, using the stable catalog
codes documented on the DesignatedServices schema (e.g.
PRO-FORMATION, RE-BROKER) and served by
GET /designated-services. This does everything the in-app
“Designated services” editor does:
isDesignatedServiceis derived from the list (non-empty →true, empty →false) — don’t send the boolean alongside the list unless they agree, or the request is rejected with422.- The customer’s risk rating is recomputed immediately, so the service’s risk tier flows into the High-risk designated service factor.
- The AML/CTF client classification is derived (new client / pre-commencement full CDD / monitoring-only).
- An empty array is an explicit “no designated services” — no CDD is
owed, so on creation the record lands with
kyc_status = NOT_REQUIRED. The same applies when you sendrisk.isDesignatedService: falsewithout a list. - On an idempotent upsert (see below) the list replaces the customer’s existing designated-service set.
Unknown or non-designated codes are rejected with
422 validation_failed — nothing is silently dropped.
Without designatedServices, the legacy risk.isDesignatedService
boolean behaves as before: it sets the flag only, and your
back-office team picks the specific services in-app.
Idempotency
- If
external_idis supplied and already exists in your organisation, the existing record is updated and the response is 200 OK. - If
emailandfullName(case-insensitive) match an existing record (and the existing record has no conflictingexternal_id), the record is updated and returned with 200 OK. Email alone is not treated as unique — family members may share one address. - Otherwise a new record is created and returned with 201 Created.
Include an Idempotency-Key header to make network retries safe for
24 hours.
curl -X POST https://app.instantcompliance.ai/api/v1/customers \
-H "Authorization: Bearer ic_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"externalId": "crm-7741",
"fullName": "Jane Doe",
"email": "jane@example.com"
}'await fetch('https://app.instantcompliance.ai/api/v1/customers', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.IC_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID()
},
body: JSON.stringify({
externalId: 'crm-7741',
fullName: 'Jane Doe',
email: 'jane@example.com'
})
});
import os, uuid, requests
requests.post(
'https://app.instantcompliance.ai/api/v1/customers',
headers={
'Authorization': f"Bearer {os.environ['IC_API_KEY']}",
'Idempotency-Key': str(uuid.uuid4())
},
json={
'externalId': 'crm-7741',
'fullName': 'Jane Doe',
'email': 'jane@example.com'
}
)
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.instantcompliance.ai/api/v1/customers",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'externalId' => 'crm-7741',
'fullName' => 'Jane Doe',
'email' => 'jane@example.com'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.instantcompliance.ai/api/v1/customers"
payload := strings.NewReader("{\n \"externalId\": \"crm-7741\",\n \"fullName\": \"Jane Doe\",\n \"email\": \"jane@example.com\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.instantcompliance.ai/api/v1/customers")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"externalId\": \"crm-7741\",\n \"fullName\": \"Jane Doe\",\n \"email\": \"jane@example.com\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.instantcompliance.ai/api/v1/customers")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"externalId\": \"crm-7741\",\n \"fullName\": \"Jane Doe\",\n \"email\": \"jane@example.com\"\n}"
response = http.request(request)
puts response.read_body{
"id": "550e8400-e29b-41d4-a716-446655440000",
"external_id": "crm-7741",
"type": "INDIVIDUAL",
"full_name": "Jane Doe",
"email": "jane@example.com",
"kyc_status": "VERIFIED",
"kyc_started_at": "2026-06-23T01:00:00Z",
"kyc_completed_at": "2026-06-23T01:12:00Z",
"identity": {
"verified_legal_name": "JANE DOE",
"verified_country": "AUS"
},
"aml": {
"status": "CLEAR",
"screened_at": "2026-06-23T01:12:00Z",
"last_reviewed_at": null,
"flags": {
"pep": false,
"sanctions": false,
"adverse_media": false,
"terrorism": false
}
},
"added_via": "INTEGRATION",
"created_at": "2026-06-22T22:14:00Z",
"updated_at": "2026-06-23T01:12:05Z"
}{
"id": "550e8400-e29b-41d4-a716-446655440000",
"external_id": "crm-7741",
"type": "INDIVIDUAL",
"full_name": "Jane Doe",
"email": "jane@example.com",
"kyc_status": "VERIFIED",
"kyc_started_at": "2026-06-23T01:00:00Z",
"kyc_completed_at": "2026-06-23T01:12:00Z",
"identity": {
"verified_legal_name": "JANE DOE",
"verified_country": "AUS"
},
"aml": {
"status": "CLEAR",
"screened_at": "2026-06-23T01:12:00Z",
"last_reviewed_at": null,
"flags": {
"pep": false,
"sanctions": false,
"adverse_media": false,
"terrorism": false
}
},
"added_via": "INTEGRATION",
"created_at": "2026-06-22T22:14:00Z",
"updated_at": "2026-06-23T01:12:05Z"
}{
"error": {
"code": "unauthorized",
"message": "Invalid or revoked API key."
}
}{
"error": {
"code": "forbidden_scope",
"message": "This API key does not have the required scope. customers:write",
"details": {
"required_scope": "customers:write"
}
}
}{
"error": {
"code": "unauthorized",
"message": "<string>",
"details": {}
}
}{
"error": {
"code": "validation_failed",
"message": "Invalid customer payload.",
"details": {
"issues": {
"email": [
"Must be a valid email address."
]
}
}
}
}{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded. Slow down and retry shortly.",
"details": {
"limit": 60,
"window_seconds": 60
}
}
}INTEGRATION, and kyc_status is derived by the same AML/CTF
rule as in-app onboarding: providing a designated service makes
customer due diligence required (NOT_STARTED — KYC is owed but not
begun); an explicit “no designated services” (empty
designatedServices list, or risk.isDesignatedService: false) lands
NOT_REQUIRED. Ingesting never triggers KYC and never charges. Start
verification when you’re ready with
POST /customers/{id}/kyc, or from inside
Instant Compliance.
Structured names
You can sendfirstName / middleName / lastName alongside fullName
(both halves or neither). API-supplied parts are stored at inferred
provenance rank, so a later correction by an officer or by the
verification provider always wins.
Existing (pre-commencement) clients
Withrisk.isNewCustomer: false you can also supply:
clientSinceDate— when the relationship began.preCommencementAssessment— the s36(4) trigger assessment for new designated services. A recorded no-trigger assessment (no significant change, or resulting risk LOW) keeps the client on the monitoring-only carve-out: services are recorded butkyc_status = NOT_REQUIRED. Without an assessment the conservative rule applies (any designated service ⇒ CDD required).preExistingServices(+preExistingServiceSince) — services you were already providing before commencement, on the “no new services” branch. History + risk only; never triggers CDD, and cannot be combined with a non-emptydesignatedServiceslist.
risk (met face-to-face, cash
business, etc.) ARE accepted on update.
Power of Attorney
Passrepresentative: { fullName, email } to record an attorney acting
under a Power of Attorney at create time. Verification links are later
addressed to the active representative.
Sole traders
Withtype: SOLE_TRADER you can also pass the individual’s registered
businessName and abn (11 digits). When an ABN is supplied the
server runs its own ABR lookup and, on an active match, marks the
business as register-verified on the record — best-effort: an ABR miss
or outage never blocks ingest, and your team can re-verify in-app.
These two fields are rejected for plain individuals.
Idempotent upsert behaviour
| Input | Match found? | Result | Status |
|---|---|---|---|
externalId supplied | Yes (same org) | Update existing record | 200 OK |
externalId supplied | No, but email matches an existing record with no externalId | Adopt + update | 200 OK |
externalId supplied | No, but email matches an existing record with a different externalId | Reject | 409 conflict |
externalId not supplied | email matches an existing record | Update existing record | 200 OK |
| Neither matches | — | Create new | 201 Created |
Retry safety
PassIdempotency-Key (a UUID or any string ≤ 255 chars). Repeated
calls with the same key within 24 hours replay the original response.
See Idempotency.Authorizations
Bearer API key issued from Settings → Developers in your
Instant Compliance organisation. Format: ic_live_….
Headers
Caller-supplied unique key for safe retries. Repeat the same key within 24 hours and we replay the original response instead of repeating the side effect. Reusing the key with a different request body returns 409 idempotency_conflict.
255Body
1 - 255255Your CRM identifier. Strongly recommended for idempotent upsert + round-tripping.
255Individual customer types — /customers only ingests these.
Entity customers (companies, trusts, partnerships, SMSFs) live on
/entities with their own EntityType.
INDIVIDUAL, SOLE_TRADER Optional structured name parts (send both firstName and lastName, or neither). Stored at inferred provenance rank — a later officer or verification-provider read can correct them.
25525525532When the relationship began (yyyy-mm-dd, not in the future). Existing clients only — requires risk.isNewCustomer: false.
The s36(4) trigger assessment for an existing (pre-commencement)
client receiving new designated services. Requires
risk.isNewCustomer: false and a non-empty designatedServices
list. Initial CDD is triggered only when BOTH limbs are met — a
significant change in the relationship AND resulting medium/high
risk. A recorded no-trigger assessment keeps the client on the
monitoring-only carve-out (kyc_status/kyb_status = NOT_REQUIRED) even though services are recorded; without an
assessment the conservative rule applies (any designated service ⇒
CDD required).
Show child attributes
Show child attributes
Designated services you were ALREADY providing before commencement ("no new services" branch). History + risk only — they never trigger CDD. Requires risk.isNewCustomer: false; cannot be combined with a non-empty designatedServices list.
6464["PRO-FORMATION", "PRO-CLIENT-MONEY"]
Approximate start date of the pre-existing service(s).
Attorney acting under a Power of Attorney. Creates a representative on the record; verification links are later addressed to them.
Show child attributes
Show child attributes
Sole-trader registered business name (type: SOLE_TRADER only) — rejected for plain individuals.
255Sole-trader ABN, 11 digits (spaces tolerated; type: SOLE_TRADER only). When supplied, the server runs its own ABR lookup and marks the business register-verified on an active match — best-effort, never blocking ingest.
Optional risk pre-answers. Your back-office team will complete the full risk assessment in-app before triggering KYC; these values seed it.
Show child attributes
Show child attributes
The specific designated service(s) provided to the customer, as
stable catalog codes. An empty array is an explicit "no designated
services". Unknown or non-designated codes are rejected with
422 validation_failed.
The catalog is also available programmatically via
GET /designated-services.
Professional services
| Code | Service |
|---|---|
PRO-FORMATION | Company or trust formation |
PRO-PURCHASE-SALE | Company or trust purchase/sale |
PRO-RESTRUCTURE | Business restructuring services |
PRO-REAL-ESTATE | Real estate professional services (conveyancing / legal) |
PRO-CLIENT-MONEY | Client money/assets management (incl. securities & virtual assets) |
PRO-FINANCING | Business financing arrangement services |
PRO-SHELF | Shelf company sales |
PRO-NOMINEE | Nominee director or shareholder services |
PRO-REGISTERED-OFFICE | Registered office address services |
Real estate
| Code | Service |
|---|---|
RE-BROKER | Real estate agency (brokering sales) |
RE-DEVELOPER | Property development sales |
Financial services
| Code | Service |
|---|---|
FIN-BANKING | Banking and deposit services |
FIN-REMITTANCE | Money transfer and remittance services |
FIN-CRYPTO | Cryptocurrency exchange services |
FIN-FX | Foreign currency exchange |
FIN-LOANS | Loans and financing services |
FIN-INVESTMENT | Investment and securities services |
FIN-ADVISORY | Financial advisory services (arranging designated services) |
FIN-INSURANCE | Insurance services (life insurance, sinking funds) |
FIN-SUPER | Superannuation fund management |
FIN-PENSION | Pension, annuity, or retirement account services |
FIN-STORED-VALUE | High-value stored value cards |
FIN-CUSTODIAL | Custodial and depository services |
FIN-PAYROLL | Payroll services for other businesses |
Other designated services
| Code | Service |
|---|---|
OTH-BULLION | Bullion trading (precious metals) |
OTH-GAMBLING | Gambling services |
OTH-HIGH-VALUE-GOODS | High-value goods dealing |
6464["PRO-FORMATION", "PRO-CLIENT-MONEY"]
Response
Existing customer updated (idempotent upsert).
Instant Compliance customer UUID.
Individual customer types — /customers only ingests these.
Entity customers (companies, trusts, partnerships, SMSFs) live on
/entities with their own EntityType.
INDIVIDUAL, SOLE_TRADER NOT_STARTED, PENDING, IN_PROGRESS, VERIFIED, FAILED, NOT_REQUIRED, AWAITING_RESUBMISSION Show child attributes
Show child attributes
How the record entered Instant Compliance.
ADMIN_MANUAL, AI_EXTRACTED, CONTACT_PORTAL, BULK_IMPORT, INTEGRATION, SYSTEM Populated only when kyc_status = VERIFIED. Deliberately minimal —
full date of birth and full address are never exposed.
Show child attributes
Show child attributes

