#Abstract
This SEP defines a standard way for stellar clients to upload KYC (or other) information to anchors and other services. SEP-6 and SEP-31 use this protocol, but it can serve as a stand-alone service as well.
This SEP was made with these goals in mind:
- interoperability
- Allow a customer to enter their KYC information to their wallet once and use it across many services without re-entering information manually
- handle the most common 80% of use cases
- handle image and binary data
- support the set of fields defined in SEP-9
- support authentication via SEP-10
- support the provision of data for SEP-6, SEP-24, SEP-31, and others
- give customers control over their data by supporting complete data erasure
To support this protocol an anchor acts as a server and implements the specified REST API endpoints, while a wallet implements a client that consumes the API. The goal is interoperability, so a wallet implements a single client according to the protocol, and will be able to interact with any compliant anchor. Similarly, an anchor that implements the API endpoints according to the protocol will work with any compliant wallet.
#Prerequisites
- An anchor must define the location of their
KYC_SERVERorTRANSFER_SERVERin theirstellar.toml. This is how a client app knows where to find the anchor's server. A client app will send KYC requests to theKYC_SERVERif it is specified, otherwise to theTRANSFER_SERVER. - Anchors and clients must support at least one of SEP-10 or SEP-45 web authentication and use it for all SEP-12 endpoints.
#API Endpoints
GET /customer: Check the status of a customers infoPUT /customer: Idempotent upload of customer infoPUT /customer/callback: Set a callback for a wallet to receive status updates from the anchorPUT /customer/verification: (Deprecated) Idempotent upload of data for verifying customer infoDELETE /customer/[account]: Deletion of customer infoPOST /customer/filesGET /customer/files
#Authentication
Clients should submit the JWT previously obtained from the anchor via the SEP-10 authentication flow. The JWT should be included in all requests as request header:
Authorization: Bearer <JWT>
#Shared, Omnibus, or Pooled Accounts
Client applications can use a single Stellar account to hold multiple users'
funds. To distinguish users that use the same Stellar account, the decoded
SEP-0010 JWT's sub value may contain a memo value after the Stellar account
(G...:2810101841641761712) OR the sub value will be a
Muxed Account (M...). The anchor should use this sub
attribute in their data model to identify unique users.
This document will refer to these accounts as shared accounts. See the SEP-10 Memos and Muxed Accounts sections for more information.
SEP-12 implementations should expect the memo in the SEP-0010 JWT's sub field
to match the memo request parameter and should return an error response if they
do not. If the JWT's sub field does not contain a muxed account or memo then
the memo request parameters may contain any value. This behavior allows shared
account owners, such as SEP-31 sending anchors, to
submit user information about their users using the memos assigned to their
users.
Note that Stellar accounts are either shared or they are not. This means anchors should ensure that a Stellar account previously authenticated with a memo should not be authenticated later without a memo. Conversely, an account that was previously authenticated without a memo should not be later authenticated as a shared account.
#Content Type
All endpoints accept in requests the following Content-Types:
multipart/form-dataapplication/x-www-form-urlencodedapplication/json
multipart/form-data should be used for requests including binary data type
values from SEP-9. Any of the above encoding schemes may be used
when requests do not include binary data.
All endpoints respond with content type:
application/json
#Customer GET
This endpoint allows clients to:
- Fetch the fields the server requires in order to register a new customer via
a
PUT /customerrequest
If the server does not have a customer registered for the parameters sent in the request, it should return the fields required in the response. The same response should be returned when no parameters are sent.
- Check the status of a customer that may already be registered
This allows clients to check whether the customers information was accepted, rejected, or still needs more info. If the server still needs more info, or the server needs updated information, it should return the fields required.
#Request
GET [KYC_SERVER]/customer?transaction_id=<transaction-id>&type=<customer-type>
GET [KYC_SERVER]/customer?account=<stellar-account>&type=<customer-type>
GET [KYC_SERVER]/customer?account=<stellar-account>&memo=<memo>&type=<customer-type>
GET [KYC_SERVER]/customer?account=<muxed-account>&type=<customer-type>
GET [KYC_SERVER]/customer?id=<customer-id>&type=<customer-type>
| Name | Type | Description |
|---|---|---|
id | string | (optional) The ID of the customer as returned in the response of a previous PUT request. If the customer has not been registered, they do not yet have an id. |
account | G... or M... or C... string | (deprecated, optional) The server should infer the account from the sub value in the SEP-10/SEP-45 JWT to identify the customer. The account parameter is only used for backwards compatibility, and if explicitly provided in the request body it should match the sub value of the decoded SEP-10/SEP-45 JWT. |
memo | string | (optional) the client-generated memo that uniquely identifies the customer. If a memo is present in the decoded SEP-0010 JWT's sub value, it must match this parameter value. If a muxed account is used as the JWT's sub value, memos sent in requests must match the 64-bit integer subaccount ID of the muxed account. If the account is a C... account, the memo must not be specified. See the Shared Accounts section for more information. |
memo_type | string | (deprecated, optional) type of memo. One of text, id or hash. Deprecated because memos should always be of type id, although anchors should continue to support this parameter for outdated clients. If hash, memo should be base64-encoded. If a memo is present in the decoded SEP-0010 JWT's sub value, this parameter can be ignored. See the Shared Accounts section for more information. |
type | string | (optional) the type of action the customer is being KYCd for. See the Type Specification below. |
transaction_id | string | (optional) The transaction id with which the customer's info is associated. When information from the customer depends on the transaction (e.g., more information is required for larger amounts) |
lang | string | (optional) Defaults to en. Language code specified using ISO 639-1. Human readable descriptions, choices, and messages should be in this language. |
#ID vs. Account & Memo
The client can always use the account and optional memo parameters to
uniquely identify a customer, and when not explicitly passed in the request
body the server can still infer this information from the decoded SEP-0010 JWT's
sub value. If a PUT /customer request has already been made for the
customer, the client should preferably use the id returned in the response
instead.
#Type Specification
Different types of customers may have different KYC requirements depending on
the action a given customer wants to take. The type parameter is used to
specify the action a customer wants to make so the server can identify what
information must be collected. SEP-12 does not define what type values are
accepted and instead leaves that to the other protocols, such as
SEP-6 and SEP-31, that use SEP-12 for the actions
associated with those protocols.
For example, if a customer is being KYC'd as a SEP-0031 sender, they may only require full name and email, but a SEP-0031 receiver would require banking information in order to receive a direct deposit.
Note that it is possible for the same customer to have different status
values for different type parameters. For example, a customer could have an
ACCEPTED status for the small-transaction-amount type parameter but have
a NEEDS_INFO status for the large-transaction-amount type. Therefore, it is
recommended to always pass the type parameter when making requests to
GET /customer or PUT /customer, even though the field is optional to
accommodate for implementations that do not require type.
If the implementor requires the same set of fields for all customers, there is
no need for the type parameter.
If transaction_id is in use, type must be provided and set (e.g. sep6,
sep31-sender, or sep31-receiver).
#Response
| Name | Type | Description |
|---|---|---|
id | string | (optional) ID of the customer, if the customer has already been created via a PUT /customer request. |
status | string | Status of the customers KYC process. |
fields | object | (optional) An object containing the fields the anchor has not yet received for the given customer of the type provided in the request. Required for customers in the NEEDS_INFO status. See Fields for more detailed information. |
provided_fields | object | (optional) An object containing the fields the anchor has received for the given customer. See Provided Fields for more detailed information. Required for customers whose information needs verification. |
message | string | (optional) Human readable message describing the current state of customer's KYC process. |
// The case when a customer has been successfully KYC'd and approved
{
"id": "d1ce2f48-3ff1-495d-9240-7a50d806cfed",
"status": "ACCEPTED",
"provided_fields": {
"first_name": {
"description": "The customer's first name",
"type": "string",
"status": "ACCEPTED"
},
"last_name": {
"description": "The customer's last name",
"type": "string",
"status": "ACCEPTED"
},
"email_address": {
"description": "The customer's email address",
"type": "string",
"status": "ACCEPTED"
}
}
}
// The case when a customer has provided some but not all required information
{
"id": "d1ce2f48-3ff1-495d-9240-7a50d806cfed",
"status": "NEEDS_INFO",
"fields": {
"mobile_number": {
"description": "phone number of the customer",
"type": "string"
},
"email_address": {
"description": "email address of the customer",
"type": "string",
"optional": true
}
},
"provided_fields": {
"first_name": {
"description": "The customer's first name",
"type": "string",
"status": "ACCEPTED"
},
"last_name": {
"description": "The customer's last name",
"type": "string",
"status": "ACCEPTED"
}
}
}
// The case when an anchor requires info about an unknown customer
{
"status": "NEEDS_INFO",
"fields": {
"email_address": {
"description": "Email address of the customer",
"type": "string",
"optional": true
},
"id_type": {
"description": "Government issued ID",
"type": "string",
"choices": [
"Passport",
"Drivers License",
"State ID"
]
},
"photo_id_front": {
"description": "A clear photo of the front of the government issued ID",
"type": "binary"
}
}
}
// The case when the Anchor is processing KYC information
{
"id": "46116754-695e-43f6-84c4-8c05e50a7b12",
"status": "PROCESSING",
"message": "Photo ID requires manual review. This process typically takes 1-2 business days.",
"provided_fields": {
"photo_id_front": {
"description": "A clear photo of the front of the government issued ID",
"type": "binary",
"status": "PROCESSING"
}
}
}
// The case when a customer has been rejected and cannot be KYC'd
{
"id": "d1ce2f48-3ff1-495d-9240-7a50d806cfed",
"status": "REJECTED",
"message": "This person is on a sanctions list"
}
// the case when the anchor requires a verification code to be sent to the server
{
"id": "d1ce2f48-3ff1-495d-9240-7a50d806cfed",
"status": "NEEDS_INFO",
"provided_fields": {
"mobile_number": {
"description": "phone number of the customer",
"type": "string",
"status": "VERIFICATION_REQUIRED"
}
}
}
#Customer Statuses
| Status | Description |
|---|---|
| ACCEPTED | All required KYC fields have been accepted and the customer has been validated for the type passed. It is possible for an accepted customer to move back to another status if the KYC provider determines it needs more info at a later date, or if the customer shows up on a sanctions list. |
| PROCESSING | KYC process is in flight and client can check again in the future to see if any further info is needed. |
| NEEDS_INFO | More info needs to be provided to finish KYC for this customer. The fields entry is required in this case. |
| REJECTED | This customer's KYC has failed and will never succeed. The message must be supplied in this case. |
#Fields
The fields object defines the pieces of information the anchor has not yet
received for the customer. It is required for the NEEDS_INFO status but may
be included with any status. Fields should be specified as an object with keys
representing the SEP-9 field names required.
Customers in the ACCEPTED status should not have any required fields present
in the object, since all required fields should have already been provided.
| Property | Type | Description |
|---|---|---|
type | enum | The data type of the field value. Can be string, binary, number, or date |
description | string | A human-readable description of this field, especially important if this is not a SEP-0009 field. |
choices | array | (optional) An array of valid values for this field. |
optional | boolean | (optional) A boolean whether this field is required to proceed or not. Defaults to false. |
#Provided Fields
The provided fields object defines the pieces of information the anchor has received for the customer. It is not required unless one or more of provided fields require verification.
| Property | Type | Description |
|---|---|---|
type | enum | The data type of the field value. Can be string, binary, number, or date |
description | string | A human-readable description of this field, especially important if this is not a SEP-0009 field. |
choices | array | (optional) An array of valid values for this field. |
optional | boolean | (optional) A boolean whether this field is required to proceed or not. Defaults to false. |
status | string | (optional) One of the values described in Provided Field Statuses. If the server does not wish to expose which field(s) were accepted or rejected, this property can be omitted. |
error | string | (optional) The human readable description of why the field is REJECTED. |
#Provided Field Statuses
| Status | Description |
|---|---|
| ACCEPTED | The field has been validated. When all required fields are accepted, the Customer Status should also be accepted. |
| PROCESSING | The field is being validated. The client can make GET /customer requests to check on the result of this validation in the future. |
| REJECTED | The field was in the PROCESSING status but did not pass validation. If the client may resubmit this field, the Customer Status should be NEEDS_INFO, otherwise it should be REJECTED. |
| VERIFICATION_REQUIRED | The field must be verified using the verification process. For example, the mobile_number field could be placed in this status until a confirmation code is sent to the customer and passed back to the this endpoint. |
#Errors
For requests containing an id parameter value that does not exist or exists
for a customer created by another anchor, return a 404 response.
{
"error": "customer not found for id: 7e285e7d-d984-412c-97bc-909d0e399fbf"
}
For invalid requests, return a 400 response describing the issue. For
example:
{
"error": "unrecognized 'type' value. see valid values in the /info response"
}
#Customer PUT
Upload customer information to an anchor in an authenticated and idempotent fashion.
PUT [KYC_SERVER || TRANSFER_SERVER]/customer
#Request
Content-Type: multipart/form-data;boundary="boundary"
--boundary
Content-Disposition: form-data; name="account"
GBORFR3GDNVZ5PLUTBDQHKGWVD26CQUHORO2T3SDQ2JPLGLUJCCA5GK6
--boundary
Content-Disposition: form-data; name="memo"
21bf91a4-7db1-401d-8108-fab7660a45d6
--boundary--
Content-Type: application/x-www-form-urlencoded
account=GBORFR3GDNVZ5PLUTBDQHKGWVD26CQUHORO2T3SDQ2JPLGLUJCCA5GK6&memo=1273187815064134326&type=sep31-sender
Content-Type: application/json
{
"account": "GBORFR3GDNVZ5PLUTBDQHKGWVD26CQUHORO2T3SDQ2JPLGLUJCCA5GK6",
"memo": "10638330804770506835",
"type": "counterparty_organization"
}
| Name | Type | Description |
|---|---|---|
id | string | (optional) The id value returned from a previous call to this endpoint. If specified, no other parameter is required. |
account | G... or M... or C... string | (deprecated, optional) The server should infer the account from the sub value in the SEP-10/SEP-45 JWT to identify the customer. The account parameter is only used for backwards compatibility, and if explicitly provided in the request body it should match the sub value of the decoded SEP-10/SEP-45 JWT. |
memo | string | (optional) the client-generated memo that uniquely identifies the customer. If a memo is present in the decoded SEP-0010 JWT's sub value, it must match this parameter value. If a muxed account is used as the JWT's sub value, memos sent in requests must match the 64-bit integer subaccount ID of the muxed account. If the account is a C... account, the memo must not be specified. See the Shared Accounts section for more information. |
memo_type | string | (deprecated, optional) type of memo. One of text, id or hash. Deprecated because memos should always be of type id, although anchors should continue to support this parameter for outdated clients. If hash, memo should be base64-encoded. If a memo is present in the decoded SEP-0010 JWT's sub value, this parameter can be ignored. See the Shared Accounts section for more information. |
type | string | (optional) The type of the customer as defined in the Type Specification. |
transaction_id | string | (optional) The transaction id with which the customer's info is associated. When information from the customer depends on the transaction (e.g., more information is required for larger amounts) |
The wallet should also transmit one or more of the fields listed in SEP-9, depending on what the anchor has indicated it needs.
When uploading data for fields specificed in SEP-9, binary
type fields (typically files) should be submitted after all other fields.
The reason for this is that some web servers require binary fields at the end
so that they know when they can begin processing the request as a stream.
#Verification
For any field that require verification (such as mobile number, or email
address), it will first receive status VERIFICATION_REQUIRED by the
application (see fields statuses for details).
Afterward, all such fields should be passed to the PUT /customer request and
suffixed with _verification string, with an appropriate verification as the
value of the field.
Example:
{
"id": "391fb415-c223-4608-b2f5-dd1e91e3a986",
"email": "test@example.com",
"mobile_number_verification": "123456"
}
In the example above customer with id 391fb415-c223-4608-b2f5-dd1e91e3a986
will update their email and send their mobile number confirmation to the
application. On success, client application will receive an ACCEPTED status
on mobile_number field if the application instantly verified the mobile
number, and PROCESSING if it needs time to process it.
#Response
If the anchor received and stored the data successfully, it should respond with
a 202 Accepted or 200 Success HTTP status code in addition to a response
body containing the customer ID.
| Name | Type | Description |
|---|---|---|
id | string | An identifier for the updated or created customer |
{
"id": "391fb415-c223-4608-b2f5-dd1e91e3a986"
}
The id can be used in future requests to retrieve the status of the customer
or update the customer's information. It may also be used in other SEPs to
identify the customer.
Anchors should return 404 Not Found for requests including an id value that
does not exist in the database. Anchors should also return 404 when the id
specified in the request was initially used to create a customer for a
different stellar account.
{
"error": "customer with `id` not found"
}
All error responses should contain details under the error key. For example:
{
"error": "'photo_id_front' cannot be decoded. Must be jpg or png."
}
{
"error": "The provided confirmation code was invalid."
}
#Customer PUT Verification
#Deprecated
This endpoint has been deprecated in favor of existing PUT /customer
endpoint.
This endpoint allows servers to accept data values, usually confirmation codes,
that verify a previously provided field via PUT /customer, such as
mobile_number or email_address. Note that while fields such as
photo_proof_residence or notary_approval_of_photo_id are verifications of
other fields described in SEP-9, the server does not require
the associated fields before verification can be accomplished, so this
endpoint would not be useful for such fields.
Fields in the VERIFICATION_REQUIRED status require a request to this
endpoint.
#Request
PUT [KYC_SERVER || TRANSFER_SERVER]/customer/verification
| Name | Type | Description |
|---|---|---|
id | string | The ID of the customer as returned in the response of a previous PUT request. |
*_verification | string | One or more SEP-0009 fields appended with _verification. |
{
"id": "391fb415-c223-4608-b2f5-dd1e91e3a986",
"mobile_number_verification": "2735021"
}
#Response
Success responses should return a 200 Success status as well as a body
matching the GET /customer response schema. The field statuses
for which verifications were sent must be updated to either PROCESSING or
ACCEPTED.
All error responses should contain details under the error key. If the id
provide is not known, a 404 status should be returned. On any other client
error, use a 400 status.
{
"id": "d1ce2f48-3ff1-495d-9240-7a50d806cfed",
"status": "ACCEPTED",
"provided_fields": {
"mobile_number": {
"description": "phone number of the customer",
"type": "string",
"status": "ACCEPTED"
}
}
}
{
"error": "The provided confirmation code was invalid."
}
#Customer DELETE
Delete all personal information that the anchor has stored about a given
customer. [account] is a Stellar account ID (G...) or contract account
address (C...) of the customer to delete. This request must be authenticated
(via SEP-10 or SEP-45) as coming from the owner
of the account that will be deleted. If account does not uniquely identify an
individual customer (a shared account), the client should include the memo
and memo_type fields in the request body.
#Request
DELETE [KYC_SERVER || TRANSFER_SERVER]/customer/[account]
| Name | Type | Description |
|---|---|---|
memo | string | (optional) the client-generated memo that uniquely identifies the customer. If a memo is present in the decoded SEP-0010 JWT's sub value, it must match this parameter value. If a muxed account is used as the JWT's sub value, memos sent in requests must match the 64-bit integer subaccount ID of the muxed account. If the account is a C... account, the memo must not be specified. See the Shared Accounts section for more information. |
memo_type | string | (deprecated, optional) type of memo. One of text, id or hash. Deprecated because memos should always be of type id, although anchors should continue to support this parameter for outdated clients. If hash, memo should be base64-encoded. If a memo is present in the decoded SEP-0010 JWT's sub value, this parameter can be ignored. See the Shared Accounts section for more information. |
#DELETE Responses
| Situation | Response |
|---|---|
| Success | 200 OK |
| Client not authenticated properly | 401 Unauthorized |
| Anchor has no information on the customer | 404 Not Found |
#Customer callback PUT
Allow the wallet to provide a callback URL to the anchor. The provided callback URL will replace (and supercede) any previously-set callback URL for this account.
Whenever the user's status field changes, the anchor will issue a POST
request to the callback URL. The payload of the POST request will be the same
as the response of GET /customer. Anchors will submit POST
requests until the user's status changes to ACCEPTED or REJECTED. If a
wallet needs to watch a user's KYC status after that, it will need to set a
callback again.
#PUT Request
PUT [KYC_SERVER || TRANSFER_SERVER]/customer/callback
| Name | Type | Description |
|---|---|---|
id | string | (optional) The ID of the customer as returned in the response of a previous PUT request. If the customer has not been registered, they do not yet have an id. |
account | G... or M... or C... string | (deprecated, optional) The server should infer the account from the sub value in the SEP-10/SEP-45 JWT to identify the customer. The account parameter is only used for backwards compatibility, and if explicitly provided in the request body it should match the sub value of the decoded SEP-10/SEP-45 JWT. |
memo | string | (optional) the client-generated memo that uniquely identifies the customer. If a memo is present in the decoded SEP-0010 JWT's sub value, it must match this parameter value. If a muxed account is used as the JWT's sub value, memos sent in requests must match the 64-bit integer subaccount ID of the muxed account. If the account is a C... account, the memo must not be specified. See the Shared Accounts section for more information. |
memo_type | string | (deprecated, optional) type of memo. One of text, id or hash. Deprecated because memos should always be of type id, although anchors should continue to support this parameter for outdated clients. If hash, memo should be base64-encoded. If a memo is present in the decoded SEP-0010 JWT's sub value, this parameter can be ignored. See the Shared Accounts section for more information. |
url | string | A callback URL that the SEP-12 server will POST to when the state of the account changes. |
#PUT Responses
| Situation | Response |
|---|---|
| Success | 200 OK |
| Client not authenticated properly | 401 Unauthorized |
| Anchor has no information on the customer | 404 Not Found |
#Callback POST Request
POST [url from PUT request]
See GET /customer reponse for the POST request fields.
In order to validate the integrity and provenance of the request, the Anchor
MUST include a signature in the HTTP Header Signature or
X-Stellar-Signature (deprecated). Wallets should support both headers until
the X-Stellar-Signature header is removed from the specification.
These headers MUST follow the specification:
t=<timestamp>, s=<base64 signature> where:
- timestamp is the current Unix timestamp (number of seconds since epoch) at the time the callback is sent. This is used to assure the freshness of the request and to prevent this request to be replayed in the future.
- base64 signature is the base64 encoding of the request signature. We
explain below how to compute and verify this signature. The signature is
computed using the Stellar private key linked to the
SIGNING_KEYfield of the anchor'sstellar.toml. Note that the timestamp and the Wallet hostname will be part of the signature to prevent replay and relay attacks.
It is the wallet's responsibility to:
- Verify the signature using the corresponding Stellar
SIGNING_KEYfield of the anchor'sstellar.toml. - Verify the freshness of the request by comparing the
timestampin the request with the current timestamp at the time of the reception and discard every request above a threshold of few seconds (1 or 2 minute(s) maximum). - Send a working callback URL to the anchor.
#VERIFY signature
- Check that callback request has
SignatureorX-Stellar-Signature(deprecated) header - Parse the header and extract:
- Key
t: timestamp - Key
s: base64 signature
- Key
- Verify the request freshness: current timestamp - timestamp < few seconds (1-2 minute(s) max)
- Extract the body of the request
- Base64 decode the base64 signature to get the signature
- Prepare the payload to verify the signature:
- The timestamp (as a string)
- The character
. - The wallet host to send the callback request to
- The character
. - The body
- Verify the signature using the correct
SIGNING_KEY
#COMPUTE signature
- Prepare the callback request body
- Prepare the payload to sign:
- Current timestamp (as a string)
- The character
. - The wallet host to send the callback request to
- The character
. - The callback request body
- Sign the payload
<timestamp>.<host>.<body>using the Anchor private key - Base64 encode the signature
- Build the
SignatureorX-Stellar-Signature(deprecated) header:Signature: t=<current timestamp>, s=<base64 encoded signature>X-Stellar-Signature: t=<current timestamp>, s=<base64 encoded signature>
#Customer Files
Passing binary fields such as photo_id_front or
organization.photo_proof_address in PUT /customer requests
must be done using the multipart/form-data content type. This is acceptable
in most cases, but multipart/form-data does not support nested data
structures such as arrays or sub-objects.
This endpoint is intended to decouple requests containing binary fields from
requests containing nested data structures, supported by content types such as
application/json. This endpoint is optional and only needs to be supported if
your use case requires accepting nested data structures in PUT /customer
requests.
Once a file has been uploaded using this endpoint, it's file_id can be used
in subsequent PUT /customer requests. The field name for the file_id should
be the appropriate SEP-9 field followed by _file_id. For
example, if file_abc is returned as a file_id from
POST /customer/files, it can be used in a PUT /customer
request like so:
{
"account": "GBORFR3GDNVZ5PLUTBDQHKGWVD26CQUHORO2T3SDQ2JPLGLUJCCA5GK6",
"memo": "21bf91a4-7db1-401d-8108-fab7660a45d6",
"memo_type": "text",
"photo_id_front_file_id": "file_abc"
}
{
"id": "2f417dab-18d2-4081-8c59-c9d3afb59d3f",
"photo_id_front_file_id": "file_abc"
}
#POST Request
POST [KYC_SERVER || TRANSFER_SERVER]/customer/files
| Name | Type | Description |
|---|---|---|
file | binary | A file to upload. The file should follow the specifications of RFC 2388 (which defines file transfers for the multipart/form-data protocol). |
#POST Response
| Name | Type | Description |
|---|---|---|
file_id | string | Unique identifier for the object. |
content_type | string | The Content-Type of the file. |
size | integer | The size in bytes of the file object. |
expires_at | UTC ISO 8601 string | (optional) The date and time the file will be discarded by the server if not referenced by the client in a PUT /customer request. |
customer_id | string | (optional) The id of the customer this file is associated with. If the customer record does not yet exist this will be null. |
{
"file_id": "file_d3d54529-6683-4341-9b66-4ac7d7504238",
"content_type": "image/jpeg",
"size": 4089371,
"customer_id": "2bf95490-db23-442d-a1bd-c6fd5efb584e"
}
#File Size Limits
A 413 Payload Too Large error should be returned when a file exceeds the server's limit, defined by the implementor. A reasonable size limit is 10MB, as most smartphone photos are around 3MB.
All other error responses should use the 400 Bad Request status and contain
details under the error key.
{
"error": "'photo_id_front' cannot be decoded. Must be jpg or png."
}
#GET Request
GET [KYC_SERVER || TRANSFER_SERVER]/customer/files
One of the following parameters is required.
| Name | Type | Description |
|---|---|---|
file_id | string | (optional) The file_id returned from a previous POST /customer/files request. The response's files list will contain a single object if this parameter is used. |
customer_id | string | (optional) The id returned from a previous PUT /customer request. The response should include all files uploaded for the specified customer. |
#GET Response
| Name | Type | Description |
|---|---|---|
files | array | A list file objects as described in the POST /customer/files response. |
{
"files": [
{
"file_id": "file_d5c67b4c-173c-428c-baab-944f4b89a57f",
"content_type": "image/png",
"size": 6134063,
"customer_id": "2bf95490-db23-442d-a1bd-c6fd5efb584e"
},
{
"file_id": "file_d3d54529-6683-4341-9b66-4ac7d7504238",
"content_type": "image/jpeg",
"size": 4089371,
"customer_id": "2bf95490-db23-442d-a1bd-c6fd5efb584e"
}
]
}
All responses should return 200 OK. If no files are found for the identifer
used, an empty list should be returned.
{
"files": []
}
#Implementations
- Flutter SDK: https://github.com/Soneso/stellar_flutter_sdk/blob/master/documentation/sdk_examples/sep-0012-kyc.md
- PHP SDK: https://github.com/Soneso/stellar-php-sdk/blob/main/examples/sep-0012-kyc.md
#Changelog
v1.15.0: Support SEP-0045 authenticated accounts (#1580)v1.14.0: Update SEP-0031 with async kyc flow (#1502)v1.13.0: Addtransaction_idfield to/customerGETandPUT(#1432)v1.12.0: DeprecatePUT /verifcationand update verification process (#1431)v1.11.1: Allow anchors to omit the deprecatedX-Stellar-Signatureheader (#1335)v1.11.0: DeprecateX-Stellar-Signaturein favor ofSignature(#1333)v1.10.0: Clarify that theaccountandmemofields should be inferred from the decoded SEP-0010 JWT'ssubvalue even when not provided in the request body.v1.9.1: Callback signature: using expected host instead of HTTP Header to validate signaturev1.9.0: Added signature requirement for thecallback