curl --request POST \
--url https://api.sand.etherfuse.com/organization/{organizationId}/verification/documents \
--header 'Authorization: <api-key>' \
--header 'Content-Type: multipart/form-data' \
--form 'id_front=@example-file;type=image/jpeg' \
--form 'id_back=@example-file;type=image/jpeg' \
--form 'proof_of_address=@example-file;type=image/jpeg' \
--form proof_of_address_type=UTILITY_BILL \
--form 'tax_document=@example-file;type=image/jpeg' \
--form country=MEXconst form = new FormData();
form.append('id_front', '<string>');
form.append('id_back', '<string>');
form.append('proof_of_address', '<string>');
form.append('proof_of_address_type', 'UTILITY_BILL');
form.append('tax_document', '<string>');
form.append('country', 'MEX');
const options = {method: 'POST', headers: {Authorization: '<api-key>'}};
options.body = form;
fetch('https://api.sand.etherfuse.com/organization/{organizationId}/verification/documents', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.sand.etherfuse.com/organization/{organizationId}/verification/documents"
files = {
"id_front": ("example-file", open("example-file", "rb"), "image/jpeg"),
"id_back": ("example-file", open("example-file", "rb"), "image/jpeg"),
"proof_of_address": ("example-file", open("example-file", "rb"), "image/jpeg"),
"tax_document": ("example-file", open("example-file", "rb"), "image/jpeg")
}
payload = {
"proof_of_address_type": "UTILITY_BILL",
"country": "MEX"
}
headers = {"Authorization": "<api-key>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text){
"organizationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "inProgress"
}{
"type": "https://docs.etherfuse.com/ramp/v2/errors/quote-expired",
"title": "Quote expired",
"status": 400,
"detail": "quote 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b has expired or does not exist",
"instance": "tag:etherfuse.com,2026-08-28:R2-10120/0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
"errors": [
{
"pointer": "#/accountDetails/clabe",
"detail": "This field is required."
}
],
"required": [
"<string>"
],
"missing": [
"<string>"
]
}{
"type": "https://docs.etherfuse.com/ramp/v2/errors/quote-expired",
"title": "Quote expired",
"status": 400,
"detail": "quote 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b has expired or does not exist",
"instance": "tag:etherfuse.com,2026-08-28:R2-10120/0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
"errors": [
{
"pointer": "#/accountDetails/clabe",
"detail": "This field is required."
}
],
"required": [
"<string>"
],
"missing": [
"<string>"
]
}{
"type": "https://docs.etherfuse.com/ramp/v2/errors/quote-expired",
"title": "Quote expired",
"status": 400,
"detail": "quote 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b has expired or does not exist",
"instance": "tag:etherfuse.com,2026-08-28:R2-10120/0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
"errors": [
{
"pointer": "#/accountDetails/clabe",
"detail": "This field is required."
}
],
"required": [
"<string>"
],
"missing": [
"<string>"
]
}{
"type": "https://docs.etherfuse.com/ramp/v2/errors/quote-expired",
"title": "Quote expired",
"status": 400,
"detail": "quote 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b has expired or does not exist",
"instance": "tag:etherfuse.com,2026-08-28:R2-10120/0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
"errors": [
{
"pointer": "#/accountDetails/clabe",
"detail": "This field is required."
}
],
"required": [
"<string>"
],
"missing": [
"<string>"
]
}{
"type": "https://docs.etherfuse.com/ramp/v2/errors/quote-expired",
"title": "Quote expired",
"status": 400,
"detail": "quote 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b has expired or does not exist",
"instance": "tag:etherfuse.com,2026-08-28:R2-10120/0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
"errors": [
{
"pointer": "#/accountDetails/clabe",
"detail": "This field is required."
}
],
"required": [
"<string>"
],
"missing": [
"<string>"
]
}Upload verification documents
Upload the customer’s documents as multipart/form-data. Send at least one of id_front, id_back, proof_of_address or tax_document.
Submit the customer’s personal data first. Until POST /organization/{organizationId}/verification has run the customer has no stages to upload against, so this answers 409.
Answers 202, not 200. We read and check every part before we answer, and send the upload to the verification provider after. Watch GET /organization/{organizationId}/kyc?requirements=true for the requirement to go satisfied.
Upload what the current stage asks for. Send a document once its requirement is listed. The exception is proof_of_address: you may send it with the identity document, before its own stage, as long as the identity stage has not been submitted yet. After that, wait for proofOfAddress to be listed. See Requirements unlock in stages.
curl --request POST \
--url https://api.sand.etherfuse.com/organization/{organizationId}/verification/documents \
--header 'Authorization: <api-key>' \
--header 'Content-Type: multipart/form-data' \
--form 'id_front=@example-file;type=image/jpeg' \
--form 'id_back=@example-file;type=image/jpeg' \
--form 'proof_of_address=@example-file;type=image/jpeg' \
--form proof_of_address_type=UTILITY_BILL \
--form 'tax_document=@example-file;type=image/jpeg' \
--form country=MEXconst form = new FormData();
form.append('id_front', '<string>');
form.append('id_back', '<string>');
form.append('proof_of_address', '<string>');
form.append('proof_of_address_type', 'UTILITY_BILL');
form.append('tax_document', '<string>');
form.append('country', 'MEX');
const options = {method: 'POST', headers: {Authorization: '<api-key>'}};
options.body = form;
fetch('https://api.sand.etherfuse.com/organization/{organizationId}/verification/documents', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.sand.etherfuse.com/organization/{organizationId}/verification/documents"
files = {
"id_front": ("example-file", open("example-file", "rb"), "image/jpeg"),
"id_back": ("example-file", open("example-file", "rb"), "image/jpeg"),
"proof_of_address": ("example-file", open("example-file", "rb"), "image/jpeg"),
"tax_document": ("example-file", open("example-file", "rb"), "image/jpeg")
}
payload = {
"proof_of_address_type": "UTILITY_BILL",
"country": "MEX"
}
headers = {"Authorization": "<api-key>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text){
"organizationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "inProgress"
}{
"type": "https://docs.etherfuse.com/ramp/v2/errors/quote-expired",
"title": "Quote expired",
"status": 400,
"detail": "quote 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b has expired or does not exist",
"instance": "tag:etherfuse.com,2026-08-28:R2-10120/0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
"errors": [
{
"pointer": "#/accountDetails/clabe",
"detail": "This field is required."
}
],
"required": [
"<string>"
],
"missing": [
"<string>"
]
}{
"type": "https://docs.etherfuse.com/ramp/v2/errors/quote-expired",
"title": "Quote expired",
"status": 400,
"detail": "quote 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b has expired or does not exist",
"instance": "tag:etherfuse.com,2026-08-28:R2-10120/0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
"errors": [
{
"pointer": "#/accountDetails/clabe",
"detail": "This field is required."
}
],
"required": [
"<string>"
],
"missing": [
"<string>"
]
}{
"type": "https://docs.etherfuse.com/ramp/v2/errors/quote-expired",
"title": "Quote expired",
"status": 400,
"detail": "quote 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b has expired or does not exist",
"instance": "tag:etherfuse.com,2026-08-28:R2-10120/0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
"errors": [
{
"pointer": "#/accountDetails/clabe",
"detail": "This field is required."
}
],
"required": [
"<string>"
],
"missing": [
"<string>"
]
}{
"type": "https://docs.etherfuse.com/ramp/v2/errors/quote-expired",
"title": "Quote expired",
"status": 400,
"detail": "quote 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b has expired or does not exist",
"instance": "tag:etherfuse.com,2026-08-28:R2-10120/0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
"errors": [
{
"pointer": "#/accountDetails/clabe",
"detail": "This field is required."
}
],
"required": [
"<string>"
],
"missing": [
"<string>"
]
}{
"type": "https://docs.etherfuse.com/ramp/v2/errors/quote-expired",
"title": "Quote expired",
"status": 400,
"detail": "quote 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b has expired or does not exist",
"instance": "tag:etherfuse.com,2026-08-28:R2-10120/0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
"errors": [
{
"pointer": "#/accountDetails/clabe",
"detail": "This field is required."
}
],
"required": [
"<string>"
],
"missing": [
"<string>"
]
}Authorizations
API key sent in the Authorization header, with no Bearer prefix.
Path Parameters
The organization this call acts for: your API key's organization or one of its customer organizations. Any other id returns 404 not-found.
Body
Front of the identity document, or its only side. JPEG, PNG or PDF, up to 10 MB. At least one of id_front, id_back, proof_of_address or tax_document must be sent.
Back of the identity document, where it has two sides. JPEG, PNG or PDF, up to 10 MB. Required whenever id_type is id_card or drivers_license, and refused for passport, which is single-sided. Both sides go in the same request: sending this without id_front is a 400, so a back cannot be added to an earlier upload on its own.
Which identity document the id_* parts are. Required whenever one is sent. Case-sensitive: send the value exactly as listed.
ID_CARD, PASSPORT, DRIVERS A document evidencing the customer's address. JPEG, PNG or PDF, up to 10 MB. Only accepted once the proof-of-address requirement is outstanding: sent earlier it is accepted with a 202 and then dropped, leaving the requirement outstanding.
Which kind of address document it is. Case-sensitive: send the value exactly as listed.
UTILITY_BILL, BANK_STATEMENT, TAX_BILL, EMPLOYMENT_CERTIFICATE, PROPERTY_DEED, INVOICE A tax document, where the customer's country requires one; in Mexico, the Constancia de Situación Fiscal. JPEG, PNG or PDF, up to 10 MB. Not recorded for a customer whose verification does not collect one. See Country-specific requirements.
Issuing country of these documents, as an ISO 3166-1 alpha-3 code. Only needed when the customer's profile has no country yet.
"MEX"
Response
Accepted and being applied. The customer's current verification status is returned.
Where the customer's verification stands after a write. status is the same value GET /organization/{organizationId}/kyc reports.
notStarted: verification has not started.inProgress: the customer started but has not submitted everything.submitted: submitted and waiting for review.approved: cleared.denied: rejected, or a previous approval was revoked.
notStarted, inProgress, submitted, approved, denied