Skip to content
Permalink
master
Switch branches/tags
Go to file
Co-authored-by: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com>
Co-authored-by: Alex Cordeiro <accordeiro@users.noreply.github.com>
13 contributors

Users who have contributed to this file

@JakeUrban @istrau2 @leighmcculloch @tomquisel @theaeolianmachine @accordeiro @tomerweller @msfeldstein @morleyzhi @marcinx @lydiat @howardtw

Preamble

SEP: 0012
Title: KYC API
Author: Interstellar
Status: Active
Created: 2018-09-11
Updated: 2021-08-17
Version 1.8.0

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_SERVER or TRANSFER_SERVER in their stellar.toml. This is how a client app knows where to find the anchor's server. A client app will send KYC requests to the KYC_SERVER if it is specified, otherwise to the TRANSFER_SERVER.
  • Anchors and clients must support SEP-10 web authentication and use it for all SEP-12 endpoints.

API Endpoints

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-10 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-10 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-data
  • application/x-www-form-urlencoded
  • application/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:

  1. Fetch the fields the server requires in order to register a new customer via a PUT /customer request

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.

  1. 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&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... string (deprecated, optional) This field should match the sub value of the decoded SEP-10 JWT.
memo string (optional) the client-generated memo that uniquely identifies the customer. If a memo is present in the decoded SEP-10 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. 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-10 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.
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. However, if a PUT /customer request has already been made for the customer, the client may 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-31 sender, they may only require full name and email, but a SEP-31 receiver would require banking information in order to receive a direct deposit. The type parameter could also be used to indicate that a customer is an organization instead of a person.

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 accomodate 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.

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 via PUT /customer/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-9 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 via PUT /customer/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-9 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 PUT /customer/verification endpoint. 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... string (deprecated, optional) This field should match the sub value of the decoded SEP-10 JWT.
memo string (optional) the client-generated memo that uniquely identifies the customer. If a memo is present in the decoded SEP-10 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. 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-10 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.

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.

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."
}

Customer PUT Verification

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-9 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 the Stellar account ID (G...) of the customer to delete. This request must be authenticated (via SEP-10) 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-10 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. 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-10 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.

It's the wallet's responsibility to send a working callback URL to the anchor.

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.

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... string (deprecated, optional) This field should match the sub value of the decoded SEP-10 JWT.
memo string (optional) the client-generated memo that uniquely identifies the customer. If a memo is present in the decoded SEP-10 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. 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-10 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

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": []
}