NAV Navbar
Logo
javascript

Welcome

Welcome to the Coinify Wallet Trading API documentation.

In this document we will take you through the process of integrating with Coinify to let your customers buy and sell cryptocurrencies through the API.


Getting started

We have made the API with simplicity in mind and we want it to be as simple as possible to get started - The following procedure describes the simplified flow that needs to be implemented in order to let a user buy and sell crypto.

  1. Register and/or authenticate user
  2. Create a price quote
  3. Create a trade
  4. Pay for trade

Here is an overview of the flows recommend to implement in order to get started.


Views to implement

In order to create a full API integration with Coinify you need to implement the following views.

  1. Quote
  2. Order summary
  3. Bank account registration
  4. Credit card payment
  5. Historical trades
  6. KYC / Customer identity verification

Introduction

This is the documentation for Coinify’s Wallet Trading API.

Environments

The following environments are available

Environment Base URL BTC Blockchain
Production https://app-api.coinify.com Mainnet
Sandbox https://app-api.sandbox.coinify.com Testnet3

Request/response format

All timestamps used in requests and responses are in ISO 8601 format.

Request format

Example HTTP POST request to request trade price quote

POST /trades/quote HTTP/2
Host: app-api.coinify.com
User-Agent: ExampleIntegration/1.0
Content-Type: application/json;charset=utf-8
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

{"baseAmount":-100,"baseCurrency":"EUR","quoteCurrency":"BTC"}

Data payloads in requests must be encoded using JSON, and accompanied by a Content-Type: application/json HTTP header.

See an example request to the right.

User agent

Example User-Agent header for (fictitious) GlobalCoin integration on Android:

User-Agent: GlobalCoin+Android/2.4.2-r4

In order for Coinify to easily track which API endpoints are being used by which partners - and to assist in integration troubleshooting, we strongly suggest that you supply a custom User-Agent HTTP header with a user agent string that is unique to your implementation.

As some parts of our API also read the user agent to optimize the experience for a specific platform, please also add Android or iOS to the user agent if you know that your end-user will be using one of those platforms.

Response format

The API always responds with a HTTP status code and usually a JSON object in the response body. This JSON object carries the data of the response, or an error object which provides more information about the error other than the HTTP status code. If the endpoint doesn’t return any data, the response body is empty.

You can determine whether a call was successful or not by checking the HTTP status code.

Success format

The body of a successful response (HTTP status code is 2xx) will always be a JSON object or an empty body (if no data needs to be returned).

The contents of the response JSON object depends entirely on the endpoint and the type of operation performed.

Error format

In addition to the JSON response body seen below, a failed request always returns with a 4xx or 5xx HTTP status code.

{
  "error": "api_key_required",
  "error_description": "Please provide a valid API key for this operation.",
  "request_id": "132baa4e-dd07-4a9a-981a-0932815e570c"
}

Whenever a request fails (the HTTP status code is 4xx or 5xx), the API returns a JSON object with two or three strings:

Signup

Currently, only trader signup is supported.

The signup endpoints reside under https://app-api.coinify.com/signup.

Trader signup

Minimal example request for POST /signup/trader

{
  "partnerId": "5f51bfe2-d08a-4b43-9d5c-405fd2f2ede6",
  "email": "trader@example.com",
  "profile": {
    "address": {
      "country": "DK"
    }
  }
}

Maximal example request for POST /signup/trader

{
  "partnerId": "5f51bfe2-d08a-4b43-9d5c-405fd2f2ede6",
  "email": "trader@example.com",
  "password": "mypassword",
  "accountType": "individual",
  "profile": {
    "address": {
      "state": "CA",
      "country": "US"
    }
  },
  "generateOfflineToken": true,
  "consentAdditionalMails": true
}

Example response for POST /signup/trader

{
  "trader": {
    "id": 754035
  },
  // Offline token, only included if requested explicitly, and only in the signup response.
  "offlineToken": "aGFja2VydHlwZXIuY29tIGlzIG15IElERQ=="
}

POST https://app-api.coinify.com/signup/trader

This endpoint allows you to sign up a new trader

Request object

The parameters in the request object are as follows:

Parameter Type Default Description
email String Required Email address of the new trader (and user)
password String null The password of the user used in combination with the email address to sign in. If omitted or null, user cannot sign in until they manually reset their password.
partnerId UUID Required Provide a Coinify partner ID to associate this trader with a specific Coinify partner.
accountType String individual Set account type if signing up as a business, can be corporate (will default to individual)
profile Object null Object with additional information about the trader.
address Object  null Object with information about the trader’s physical address.
    →state String null ISO 3166-2 state/province. Required if country is ‘US’
    →country String Required ISO 3166-1 alpha-2 country code.
trustedEmailValidationToken String null (Only for trusted Coinify partners, partnerId must be provided) Signed message from partner for automatic validation of email. See Trusted email validation.
generateOfflineToken Boolean false Also generate and return an offline token for the new trader. If set to true, an offline token is created for the new trader and returned in the response with the key offlineToken.
consentAdditionalMails Boolean Required Determines whether the user consents to receive additional email from Coinify.
Response object

The success response object contains the following fields:

Key Type Description
trader Object See example
offlineToken String (Optional) An offline token for the new trader. Only created and returned if the generateOfflineToken request parameter is explicitly set to true
Error object

If the signup request fails for some reason, the response contains the first error encountered during the signup process:

HTTP status code Error code Description
201 N/A Sign up successful
400 invalid_request There was something wrong with the signup request. See the error_description argument for a specific, human-readable error message.
400 email_address_and_partner_id_in_use The provided email address is already associated with an existing trader that belongs to the specified partner.
400 invalid_trusted_email_validation_token The supplied trustedEmailValidationToken was invalid.
500 internal_error An internal error happened during signup. Please try again later.

Email validation

When signing up a trader with Coinify, we have to ensure that the email address provided is valid and that the new trader can receive emails sent to that address. This can happen in one of two ways:

Trusted validation

While the manual validation described previously is easier to implement, it sometimes doesn’t make sense to perform an additional email validation from a user experience perspective - if the user is signing up for Coinify through a partner, the partner will most likely already have validated the user’s email address. In this case, the partner can issue a signed message to Coinify stating that the user’s email address has already been validated by the partner.

Coinify then trusts the partner to have validated the email address themselves, and the email address is immediately marked as validated, and the user won’t have to perform any manual action to validate the email address to Coinify.

Example of 2048-bit RSA public key in PEM format.
Coinify must have the partner’s public key (RSA/EC) in advance for trusted validation to work

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3joQFkg6LXuR2PvFDiZn
fOlF5edR4nEUxTErLlvjg00cus/MvgP8QwKhVYMlfNqEqa2N6vWeLyT0reJ8KKBl
6M7BKnxRmPwh0dx6S8E+tdHMoryjwopCDUlPpxoB8lw9/aRkFm4tLKzHH53Ypijl
9U+4M28KQHUnSq+rfskNXI7bgN1jIX7CAECqjQLExNsu7iAit5RsUBCiEb+p+HVy
mQ0S30CQJy93NT5AuWTrWKiNv1rUXYZ6YWODWLNCZ+GHJG6U0sV36EooFGmxadpH
UVo4HGMPEHNu07HK0CYlJgRF1h6z3ea6uCHVap4F6Kwa4chdKJm+H6ukCElPGaW1
YwIDAQAB
-----END PUBLIC KEY-----

In essence, for trusted validation to work, the partner must have generated a cryptographic key pair and sent the public key to Coinify. Before the signup, the partner then signs a message containing the same email address as the email parameter in the signup. If the email address of the signed message equals that of the signup request and the signature of the message is valid using the public key, Coinify considers the email address as valid. If the email addresses differ or the signature is invalid, the signup request will fail with a invalid_trusted_email_validation_token error code.

Signed message details

The signed message containing the email address must be a JSON Web Token (JWT) and passed in the signup request as the trustedEmailValidationToken parameter.

The message must be signed using one of the following algorithms:

JWT alg name Algorithm
RS256 RSASSA using SHA-256 hash algorithm
RS384 RSASSA using SHA-384 hash algorithm
RS512 RSASSA using SHA-512 hash algorithm
ES256 ECDSA using P-256 curve and SHA-256 hash algorithm
ES384 ECDSA using P-384 curve and SHA-384 hash algorithm
ES512 ECDSA using P-521 curve and SHA-512 hash algorithm
JWT Header

Example JWT header using RS256 algorithm

{
  "alg": "RS256",
  "typ": "JWT"
}

The header must contain exactly two values (see example to the right):

Key Type Description
typ String JWT identifier. Must contain the string "JWT".
alg String Signature algorithm. Must be one of the allowed signature algorithms listed above.
JWT payload

Example JWT payload for email address johndoe@example.com

{
  "email": "johndoe@example.com",
  "exp": 1467331200  // Expires at 2016-07-01 00:00:00 UTC
}

The payload of the JWT can contain the following values (see example to the right):

Key Type Default value Description
email String Required Email address. The valid email address.
exp Integer Never expire Expiration time. Unix timestamp for when this JWT will no longer be valid.
If given, the message will only be valid for validating the email address up until the given time.
If not given, the message will always be valid.
JWT example

Example JWT token (with added line break)

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.
eyJlbWFpbCI6ImpvaG5kb2VAZXhhbXBsZS5jb20iLCJleHAiOjE0NjczMzEyMDB9.
qpn5iey9H86OwxsH8npw_IkmRIE3nxJKgl3FqRP0lSjc5XCA71XcMppwU2WqELgu
Cl7arKSmiHdp9GTu_sfXTk1bSiZMYfpfO810y02l_IrSh4YcZeYko3wW2QpVfgZF
lqwoNQcBM878mveUAmRKfhtQjohHcjLb5YDS2uVf2AFvXFUS443HL25_DC1IuUzr
YCv9pMBI4YHVxWIPU25EERMA03pa0O1XoF9LJR4uTBmKcQTC94IDbbf5GKz29Htk
kEvmx9z5cl0TZGxsRSwzsF0xI6v1taGzKo0NCn8AXpXkjiuffHm_Ml_v5L-bHGhZ
EYs25682iuS6K9Nl1FAggw

The above JWT contains the following header and payload. The token is signed with the private key matching the public key example shown previously.

// Header
{
  "alg": "RS256",
  "typ": "JWT"
}
// Payload
{
  "email": "johndoe@example.com",
  "exp": 1467331200  // Expires at 2016-07-01 00:00:00 UTC
}

Authentication

In order to call most of the API endpoints, one needs to obtain an access token. This token is currently obtainable by authenticating yourself to the system.

This section describes how to do just that.

Example Authorization header with an access token

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Once you have obtained your access token, you must include it in all requests that require authentication. You do so by adding a Authorization header to the request with the value Bearer <access-token> as seen in the example on the right.

Failure to include an access token when accessing protected resources will result in a 401 Unauthorized response, while a 403 Forbidden means that you don’t have access to the protected resource.

Authenticate

Example success response body

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ",
  "token_type": "bearer",
  "expires_in": 1200, // Lifetime of access token in seconds
  "refresh_token": "wt5RoH8i6HkSQvI8kFpEBLEIB6lw8lOpYKHEz0ND9znDaAOtH1dFI32GqhvT9PGC"
}

POST https://app-api.coinify.com/auth

In order to obtain an access token, you must authenticate yourself using one of the authentication methods.

When a successful authentication has been performed, you will receive an access token and a refresh token as exemplified in the response to the right. You must use this access token to authenticate yourself to most other endpoints.

You must include a grant_type parameter (string) in the request which defines the authentication method. The authentication endpoint currently supports the following grant_types:

The possible response types, regardless of the grant_type used, are listed in the following table:

HTTP Response code JSON data
200 OK Success, Response with a fresh authentication token. See example to the right.
400 Bad request Error, see OAuth2 Error Response for possible error codes.

Email authentication

Example request with grant_type == "password"

{
  "grant_type": "password",
  "email": "user@coinify.com",
  "partnerId": "5f51bfe2-d08a-4b43-9d5c-405fd2f2ede6",
  "password": "password"
}

Use this grant_type to authenticate a user with a combination of email address and password.

Request parameter Type Description
grant_type string password - Authenticate using email and password.
partnerId string Id of the partner the user belongs to.
email string Email address of user to authenticate.
password string User’s password.

Refresh access token

Example request with grant_type == "refresh_token"

{
  "grant_type": "refresh_token",
  "refresh_token": "wt5RoH8i6HkSQvI8kFpEBLEIB6lw8lOpYKHEz0ND9znDaAOtH1dFI32GqhvT9PGC"
}

The access token that you received upon authentication is only valid for 20 minutes, while the refresh token is valid for 24 hours. After the access token has expired, you can obtain a new one by either (1) re-authenticating or by (2) refreshing your token. This endpoint allows you to refresh your token without requiring the user to re-authenticate. For this reason, the Log in/Log out button is automatically removed from the trade widget menu to provide a better user experience.

Request parameter Type Description
grant_type string refresh_token - Refresh using a refresh token.
refresh_token string The refresh token obtained from the previous authentication response.

Offline token

Example request with grant_type == "offline_token"

{
    "grant_type": "offline_token",
    "offline_token": "A3873gbnvgHgniehfq8QHkSQvI8kFpEBsiudhAS83Nat2g7ASB2GqhvT9PGC"
}

Example response for authentication request

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ",
  "refresh_token": "wt5RoH8i6HkSQvI8kFpEBLEIB6lw8lOpYKHEz0ND9znDaAOtH1dFI32GqhvT9PGC",
  "token_type": "bearer",
  "expires_in": 1200 // Lifetime of access token in seconds
}

The offline token is used exactly as you would use a refresh token, except that it doesn’t expire.

This token does not expire (it’s life-long).

Request parameter Type Description
grant_type string offline_token - Authenticate using an offline token, that does not expire.
offline_token string The offline token obtained upon signup.

Sign out (Revoke refresh token)

Example request

{
  "token_type_hint": "refresh_token",
  "token": "wt5RoH8i6HkSQvI8kFpEBLEIB6lw8lOpYKHEz0ND9znDaAOtH1dFI32GqhvT9PGC"
}

POST https://app-api.coinify.com/auth/revoke

If the user wants to sign out, the client will have to revoke the refresh token and “forget” the current access token. This endpoint allows you to revoke a refresh token, so it is no longer valid.

Request parameter Type Description
token_type_hint string Must be refresh_token
token string The refresh token obtained from the authentication response

The possible response types are listed in the following table:

HTTP Response code JSON data
204 No Content Success, Empty response body
400 Bad request Error, see OAuth2 Error Response for possible error codes.

Deactivate account

PUT https://user.coinify.com/me/deactivate

If you wish to deactivate your account, you can make the above request. The only expected payload is the Authentication HTTP Header.

You can only deactivate your own account.

A deactivated account can not obtain an access token and thus is unable to use the parts of the API that require an access token.

Response
HTTP Response code JSON data
204 No Content Success, Empty response body
500 Server Error Error, fulfilling the request.

Partner

Approximate quote

Example of query parameters

{
  "baseAmount": -100,
  "baseCurrency": "EUR",
  "quoteCurrency": "BTC",
  "transferInMedium": "card",
  "transferOutMedium": "blockchain"
}

Example response

{
  "quoteAmount": 0.01234
}

GET https://app-api.coinify.com/partners/:partner-id/approximate-quote

Endpoints returns the approximate quote. If transfer media are provided transfer fees are also used for calculating the quote amount.

See trade quote for details and explanation of the quote.

Query params
Parameter Description
baseAmount Amount to get a price quote for. Denominated in baseCurrency.
baseCurrency Currency to get a quote for.
quoteCurrency Currency to get the quote in.
transferInMedium (optional) Transfer in medium see payment methods.
transferOutMedium (optional) Transfer out medium see payment methods.

Trader KYC

Example of JWT payload

{
  "traderId": 123,
  "externalId": "id-of-the-partner-kyc-review"
}

Example of a request

{
  "traderId": 123,
  "externalId": "id-of-the-partner-kyc-review",
  "state": "completed",
  "approveTime": "2018-11-19T01:23:35Z",
  "createTime": "2018-11-19T00:23:35Z",
  "pep": {
    "approved": true,
    "approveTime": "2018-11-19T01:23:35Z"
  },
  "sanction": {
    "approved": true,
    "approveTime": "2018-11-19T01:23:35Z"
  },
  "profile": {
    "citizenCountry": "DK",
    "name": "John doe",
    "dateOfBirth": "1988-12-14",
    "address": {
      "street": "London Street 123",
      "city": "London",
      "postalCode": "2001",
      "country": "UK"
    }
  }
}

Example of a response

{
  "id": "6b54f095-db25-4507-9912-0461fe70768f",
  "externalId": "id-of-the-partner-kyc-review"
}

POST https://app-api.coinify.com/partners/:partner-id/kyc

This endpoint will be used by partners to send us KYC review information for traders who are KYC approved by the partner.

Please contact Coinify if you want to use this endpoint.

Authentication

To authenticate request we will use public / private key from trusted email validation.

JWT payload will be different, see example to the right

The JWT token should be sent as a Bearer token in the Authorization header like this Authorization: Bearer <jwt_token>

Request params

Example of JWT payload

{
  "traderId": 123,
  "externalId": "id-of-the-partner-kyc-review"
}
Parameter Type Default Description
traderId Integer Required ID of the trader
externalId String Required Identifier for this KYC review
state String Required State of the review, only completed so far
approveTime ISO 8601 Required Timestamp of approval
createTime ISO 8601 Required Timestamp of creation
pep Object Required Object containing PEP details (politically exposed person)
pep.approved Boolean Required If profile was pep checked
pep.approveTime ISO 8601 Required Timestamp of approval
sanction Object Required Object containing sanction check details
sanction.approved Boolean Required If profile was sanction checked
sanction.approveTime ISO 8601 Required Timestamp of approval
profile Object Required Containing profile information
profile.citizenCountry ISO 3166 alpha-2 Required Citizen country code
profile.name String Required Full name of the trader
profile.dateOfBirth String Required Date of birth (YYYY-MM-DD)
profile.address Object Required Object containing address details
profile.address.street String Required Street with number
profile.address.city String Required City
profile.address.state String Required State
profile.address.postalCode String Required Postal code
profile.address.country ISO 3166 alpha-2 Required Country of residence
Response

If the response status code is 500 or larger, the partner should resend request using some exponential backoff strategy.

HTTP Response code JSON data
200 OK Success, review already exists (nothing changed).
201 Created Success.
400 Bad request Error interpreting the request.
401 Unauthorized Error, authentication failed.

A 200 or 201 response object contains the following fields:

Key Type Description
id String (UUID) Unique identifier for the KYC review. Use this id to upload supporting documents
externalId String The externalId provided in the request

Upload supporting documents

Example of JWT payload

{
  "reviewId": "6b54f095-db25-4507-9912-0461fe70768f",
  "externalId": "your-reference-for-this-document"
}

Raw HTTP Request example

POST /partners/:partner-id/kyc/:review-id/documents HTTP/1.1
Host: app-api.coinify.com
Authorization: Bearer <jwt_token>
Content-Type: image/jpeg
Content-Length: 284
X-Upload-Filename: my_filename.jpg
X-Upload-Document-Type: national_id
X-Upload-External-Id: your-reference-for-this-document

<<raw image content>>

Example response

{
  "id": "3ebb9ba1-015e-4a71-816d-56f57f711f4c",
  "externalId": "your-reference-for-this-document"
}

POST https://app-api.coinify.com/partners/:partner-id/kyc/:review-id/documents

You can upload supporting documents to an existing KYC review by making a POST request to this endpoint, where :review-id is the id returned from the POST /partners/:partner-id/kyc endpoint.

The body of the request must contain the raw document data

Request headers

All of the HTTP headers in below table are required. Failure to provide either will result in an error

Name Description
Authorization Must contain Bearer <jwt_token>
Content-Type MIME type of supporting document
X-Upload-External-Id Your (partner’s) reference for this document. This must match the externalId value in the supplied <jwt_token>.
X-Upload-Document-Type Document type. Must be one of the following values: passport, national_id, or drivers_license accompanied by a selfie.
X-Upload-Filename Original filename of document
JWT payload

The JWT Bearer token supplied in the Authorization header must contain the following data: (see example to the right)

Key Type Description
reviewId String (UUID) Identifier for the review to upload document to. Must match the :review-id path parameter.
externalId String Unique identifier for the supporting document. Must match the X-Upload-External-Id header value provided in the request
Response

If the response status code is 500 or larger, the partner should resend request using some exponential backoff strategy.

HTTP Response code JSON data
201 Created Success
400 Bad request Error interpreting the request.
401 Unauthorized Error, authentication failed.

A 200 or 201 response object contains the following fields:

Key Type Description
id String (UUID) Unique identifier for the supporting document.
externalId String The X-Upload-External-Id header value provided in the request

Supported countries

Example of the response

{
  "AT": {
    "supported": true,
    "supportedTransferInMedia": [
      "card"
    ]
  },
  "BE": {
    "supported": true,
    "supportedTransferInMedia": [
      "card",
      "bank",
      "blockchain"
    ]
  },
  "BG": {
    "supported": true,
    "supportedTransferInMedia": [
      "card",
      "bank",
      "blockchain"
    ]
  },
  "HR": {
    "supported": true,
    "supportedTransferInMedia": [
      "card",
      "bank",
      "blockchain"
    ]
  },
  ...
  "US": {
    "supported": false,
    "states": {
      "LA": {
        "supported": false,
        "supportedTransferInMedia": []
      },
      "OR": {
        "supported": false,
        "supportedTransferInMedia": [
          "card",
          "bank",
          "blockchain"
        ]
      },
      "CT": {
        "supported": false,
        "supportedTransferInMedia": [
          "card",
          "bank",
          "blockchain"
        ]
      }
      ...
    }
  }
}

GET https://app-api.coinify.com/countries/:id

This endpoint will be used by partners to get the list of supported countries and the transfer media supported in each country/state.

Request params
Parameter Type Description
id UUID UUID provided to the partner
Response
HTTP Response code JSON data
200 OK Success, response as shown to the right
400 Bad request Error invalid_request: id is invalid.
404 Bad request Error not_found: id is missing from the request.

Payment methods

GET https://app-api.coinify.com/partners/:id/payment-methods

This endpoint will be used by partners to get list of supported payment methods and minimum / maximum limits before signing up the trader.

Because we use individual risk assessment limits might be different for the individual trader.

You can find more info on the blockchain network and smart contract address behind each supported cryptocurrency here for:

Request params
Parameter Type Description
id UUID UUID provided to the partner
Response

Response is similar to Payment methods endpoint from trader requests, except canTrade and cannotTradeReasons because they apply to individual traders.

HTTP Response code JSON data
200 OK Success, response as shown to the right
400 Bad request Error invalid_request: id is invalid.
404 Bad request Error not_found: id is missing from the request.

User

A user is the entity used for authentication.

Request email verification

Used to request a verification email. Should only be used if the user missed the email sent after registration.

POST /users/me/request-email-verification

Response and request are both empty

HTTP Response code JSON data
200 OK Success, empty body
Error codes Description
invalid_request Email already verified

Update password

Example request for PATCH /users/me

{
  "oldPassword": "myoldpassword1337",
  "newPassword": "mynewpassword1337"
}

Example response for PATCH /users/me

{
  "id": 1234,
  "email": "user@email.com"
}

PATCH users/me

Update information about a specific user.

Request parameter Type Description
oldPassword String The old password the user wants to change
newPassword String The new password the user wants to use
HTTP Response code JSON data
200 OK Success, user response as shown to the right
400 Bad request Error interpreting the request.
401 Unauthorized Error, access token missing.
Error codes Description
wrong_password The provided oldPassword doesn’t match the existing password.
invalid_request Error in the request body.

Verify Email

Example request for POST /users/me/verify-email

{
  "code": "123654"
}

POST /users/me/verify-email

Request parameter Type Description
code String 6 digit code supplied by email
HTTP Response code JSON data
200 OK Success
400 Bad request Error
404 Not found Error
Error codes Description
invalid_verification_code Invalid code.
verification_attempts_exceeded Invalid code has been posted more than 5 times.

Request password reset

Starts the password reset flow for given email.

Example request for POST /users/request-password-reset

{
  "email": "user@email.com",
  "partnerId": "5f51bfe2-d08a-4b43-9d5c-405fd2f2ede6"
}

POST users/request-password-reset

Update information about a specific user.

Request parameter Type Description
email String Email of the user for whom the password should change.
partnerId UUID partnerId for partner associated with the user.
HTTP Response code JSON data
200 OK Success
400 Bad request Error
Error codes Description
invalid_request Error in the request body.

Validate security code

Validate security code sent via email when reset password has been requested.

Example request for POST /users/validate-security-code

{
  "email": "test@coinify.com",
  "partnerId": "5f51bfe2-d08a-4b43-9d5c-405fd2f2ede6",
  "code": "123456"
}

Example response from the above request

{
  "id": 1234,
  "time": 123559953254,
  "token": "secret-token-used-for-reset-password"
}

POST /users/validate-security-code

Validate security code sent by email and provide response used for resetting password (see next step).

Request parameter Type Description
email String
partnerId UUID Partner ID.
code String Security code provided in email.
HTTP Response code JSON data
200 OK Success
400 Bad request Error
404 Not found Error user or security code is not found.
Error codes Description
security_code_not_found User or security code is not found.
invalid_security_code Security code is invalid.
security_code_attempts_exceeded Max attempts exceeded.

Reset password

Sets new password for the user.

Example request for POST /users/reset-password

{
  "id": 1,
  "token": "1234567890asdfghjk",
  "time": 123456789,
  "password": "mynewpassword"
}

POST /users/reset-password

Request parameter Type Description
id Integer ID of the user who will get new password.
token String Token for checking validity of the request.
time Integer Time of the request.
password String New password.
HTTP Response code JSON data
200 OK Success
400 Bad request Error interpreting the request.
404 Not found Error user is not found.
Error codes Description
invalid_password_reset_token The provided token is not valid.
user_not_found User with given id does not exist.

Reset offline token

Example of JWT payload

{
  "email": "trader@123.com"
}

Example of a request

{
  "email": "trader@123.com",
  "partnerId": "5f51bfe2-d08a-4b43-9d5c-405fd2f2ede6"
}

Example of a response

{
  "offlineToken": "new-offline-token"
}

Endpoint to reset the offline token for a user. Old one will be invalidated, and a new one will be issued.

POST /users/reset-offline-token

Authentication

To authenticate request we will use public / private key from trusted email validation.

JWT payload will be different. The payload must contain the email equal to the request body email, see example to the right.

The JWT token should be sent as a Bearer token in the Authorization header like this Authorization: Bearer <jwt_token>

Request parameter Type Description
email String Email of the user
partnerId UUID Partner ID
HTTP Response code JSON data
200 OK Success
400 Bad request Error interpreting the request.
401 Unauthorized Error access not granted.
404 Not found Error user is not found.
Error codes Description
missing_argument Request param missing.
user_not_found User with given email does not exist.
invalid_partner_token Partner token not provided or invalid.

Trader

A trader is an entity that can perform trades.

You can create a trader entity with the trader signup endpoint.

Mandatory trader information

We distinguish between two types of trader accounts, individual and corporate. In order for the trader to be able to complete their trades they must provide some mandatory information first. Please find the mandatory API endpoints for each trader account type below.

Individual trader mandatory endpoints:

Corporate trader mandatory endopoints:

Once the corporate trader provides the above listed information, they will receive a KYB email to provide necessary documents.

Get trader information

Example response for GET /traders/me

{
  "id": 754035,
  "email": "trader@example.com",
  "personalInformation": {
    "name": "Jens Jensen"
  },
  "corporateDetails": null,
  "isEmailVerified": true,
  "identificationState": "approved",
  "corporateDetailsState": "N/A",
  "addressState": "approved",
  "sourceOfFundsState": "approved",
  "kybState": "N/A",
  "shareholderRelationState": "N/A",
  "proofOfSourceOfFundsState": "pending",
  "enhancedProofOfSourceOfFundsState": "pending",
  "pepState": "pep",
  "tradeSubscriptionsAllowed": false,
  "consentAdditionalMails": true,
  "accountType": "individual"
}

GET https://app-api.coinify.com/traders/me

This endpoint returns a trader object for your trader, provided you are authorized as a trader.

Key Type Description
id Integer ID of the trader
email String Email address of the trader
personalInformation Object object containing personal information (null until name of trader is set)
name string Name of trader
corporateDetails Object Object containing corporate details ( will be null until corporate details is provided )
name string name of corporation
isEmailVerified Boolean Is email verified
identificationState String State of traders identity verification. Possible states are approved, N/A and pending
corporateDetailsState String State of traders corporate details. Possible state are approved, N/A and pending
addressState String State of trader address. Possibles states are approved, pending
sourceOfFundsState String State of traders source of funds. Possible states are approved, N/A and pending
kybState String State of kyb process. Possible states are approved, documents_requested, N/A and pending
enhancedDueDiligenceState String State of trader’s Enhanced Due Diligence (EDD). If the trader is marked as high-risk, Coinify will do the EDD and a questionnaire will be sent to the trader’s email address. Possible states are pending, questionnaire_sent, approved, or rejected
shareholderRelationState String State of shareholderRelation, Possible states are pending, related, not_related, ´N/A`
proofOfSourceOfFundsState String State of traders proof of source of funds. If the trader traded for more than 10000 EUR we will sent him an email with proof of source of funds questionaire and he won’t be allowed to trade until it is approved. Possible states are approved, rejected, questionaire_sent and pending
enhancedProofOfSourceOfFundsState String State of traders enhanced proof of source of funds. Possible states are approved and pending
pepState String State of traders PEP. Possible states are pep, non_pep, pep_relative, N/A and pending
consentAdditionalMails Boolean Determines whether the user consents to receive additional email from Coinify. This is to comply with GDPR
accountType String Determines the trader account type, possible account types are individual and corporate
Response
HTTP Response code JSON data
200 OK Success, response as shown to the right.
404 Not found Error, trader not found.

Trader Identification

In order to comply with financial regulations, all traders must undergo a KYC (Know-Your-Customer) review before they can start trading. A KYC review typically revolves around verifying the identity of the trader.

This section deals with the mechanisms for doing so.

Identification attempt object

Example of an identification attempt:

{
  "id": "0cf12488-0dcf-4f99-96c4-e1ec879c906d",
  "state": "approved",
  "createTime": "2019-11-26T07:45:44.852Z",
  "approveTime": "2019-11-26T07:49:19.126Z",
  "expireTime": "2028-03-04T00:00:00.000Z",
  "redirectUrl": "https://www.example.com/redirect-client-to-this-url",
  "sdkToken": null,
  "returnUrl": null
}

An identification attempt is represented as an object as seen to the right. The table below describes each of the properties in the object.

Key Type Description
id String Identifier (UUID) for the identification attempt
state Identification attempt state State of the identification attempt
createTime ISO 8601 time Timestamp for when this identification attempt was first initiated.
approveTime ISO 8601 time or null Timestamp for when this identification attempt was approved
expireTime ISO 8601 time or null Timestamp for when this identification attempt will expire
redirectUrl URL (String) URL to redirect the user to in order to perform the identification
sdkToken JWT (String) Deprecated The parameter is till returned in the response but will always be null
returnUrl URL (String) or null URL to return to when identification attempt is submitted or failed. Returning after identification flow below for more information.
rejectReason Identification attempt rejection reason or null Machine-readable reason for rejecting this identification attempt, if state is 'rejected'.

Identification attempt state

An identification attempt can be in a number of well-defined states throughout its lifetime.

State Description
pending (Starting state) Identification attempt has been initiated, but is waiting for action from the end-user.
processing (Intermediate state) Identification attempt is currently being processed.
approved (Final state) Identification attempt was successfully approved. (This state is also used if the identification attempt was manual and accepted)
expired (Final state) Identification attempt expired because the documents used to identify with has expired
rejected (Final state) Identification attempt was rejected

Identification attempt rejection reasons

If an identification attempt state is rejected, the identification attempt contains a rejectReason providing a reason for the rejection. The below table lists all currently possible reasons:

Rejection reason Description
DENIED Denied without any further reason
DENIED_UNSUPPORTED_ID_TYPE The used identification was not supported
DENIED_UNSUPPORTED_ID_COUNTRY The used identification was issued in a country not supported by Coinify
DENIED_MISSING_DATE_OF_BIRTH The used identification didn’t contain a date of birth
DENIED_YOUNGER_THAN_18_YEARS The identified person must be 18 years or older
DENIED_EXPIRED_IDENTIFICATION The used identification has expired
DENIED_FAILED_IDENTITY_VERIFICATION We were unable to ensure that the person submitting the identification was the same person in the ID photo
ERROR_NOT_READABLE_ID.PHOTOCOPY_BLACK_WHITE
ERROR_NOT_READABLE_ID.PHOTOCOPY_COLOR
ERROR_NOT_READABLE_ID.DIGITAL_COPY
ERROR_NOT_READABLE_ID.NOT_READABLE_DOCUMENT
.BLURRED
ERROR_NOT_READABLE_ID.NOT_READABLE_DOCUMENT
.BAD_QUALITY
ERROR_NOT_READABLE_ID.NOT_READABLE_DOCUMENT
.MISSING_PART_DOCUMENT
ERROR_NOT_READABLE_ID.NOT_READABLE_DOCUMENT
.HIDDEN_PART_DOCUMENT
ERROR_NOT_READABLE_ID.NOT_READABLE_DOCUMENT
.DAMAGED_DOCUMENT
ERROR_NOT_READABLE_ID.NO_DOCUMENT
ERROR_NOT_READABLE_ID.SAMPLE_DOCUMENT
ERROR_NOT_READABLE_ID.MISSING_BACK
ERROR_NOT_READABLE_ID.WRONG_DOCUMENT_PAGE
ERROR_NOT_READABLE_ID.MISSING_SIGNATURE
ERROR_NOT_READABLE_ID.CAMERA_BLACK_WHITE
ERROR_NOT_READABLE_ID.DIFFERENT_PERSONS_SHOWN
ERROR_NOT_READABLE_ID.MANUAL_REJECTION
MANUAL_REVIEW The identification attempt is under manual review by Coinify compliance. This process can last betwen 1-3 days. Coinify will communicate the final result with the trader via email and a callback will be sent to your webhook accordingly. As an API integrated Partner you should inform the user about this accordingly.
NO_ID_UPLOADED No identification was successfully submitted.

Initiate identification attempt

Example request to POST /kyc/identification-attempts

{
  "returnUrl": "https://mypage.com/ida_complete"
}

Example response from POST /kyc/identification-attempts

{
  "id": "0cf12488-0dcf-4f99-96c4-e1ec879c906d",
  "state": "pending",
  "createTime": "2019-11-26T07:45:44.852Z",
  "approveTime": null,
  "expireTime": null,
  "redirectUrl": "https://www.example.com/redirect-client-to-this-url",
  "returnUrl": "https://mypage.com/ida_complete",
  "sdkToken": null
}

POST https://app-api.coinify.com/kyc/identification-attempts

Initiates an identification attempt. The user must be redirected to the URL specified in the redirectUrl parameter of the response in order to actually identify themselves.

Request object

The parameters in the request object are as follows:

Parameter Type Default Description
returnUrl URL (String) No value URL to return to after the process is done when using the redirectUrl. See Returning after identification flow below for more information.
deviceType String No value Type of device on which the user will perform identification. Used to provide the best possible user experience. Possible values are android (Android phone/tablet), ios (iPhone/iPad), desktop (Non-mobile device). If left blank, auto-detection will be attempted from the User-Agent HTTP header of the request.
Response
HTTP Response code JSON data
201 Created Success, Identification attempt object as shown to the right.
400 Bad request Error interpreting the request.
401 Unauthorized Error, access token missing.
403 Forbidden Error, invalid access token.
Returning after identification flow

If a returnUrl is specified during the initiate identification attempt API call, then the user will be redirected to that URL once they have submitted (or failed to submit) their identification.

The returnUrl is augmented with a result query parameter containing either a 'submitted' or 'failed' value. Examples:

returnUrl Result of flow Actual URL that user is redirected to
https://mypage.com/ida_return Documents submitted https://mypage.com/ida_return?result=submitted
https://mypage.com/ida_return Failed https://mypage.com/ida_return?result=failed
https://mypage.com/ida_return?other_query_parameter=1234 Documents submitted https://mypage.com/ida_return?other_query_parameter=1234&result=submitted
https://mypage.com/ida_return?other_query_parameter=1234 Failed https://mypage.com/ida_return?other_query_parameter=1234&result=failed

Get identification attempt

Example request for GET /kyc/identification-attempts/0cf12488-0dcf-4f99-96c4-e1ec879c906d

{
  "id": "0cf12488-0dcf-4f99-96c4-e1ec879c906d",
  "state": "approved",
  "createTime": "2019-11-26T07:45:44.852Z",
  "approveTime": "2019-11-26T07:49:19.126Z",
  "expireTime": "2028-03-04T00:00:00.000Z",
  "redirectUrl": "https://www.example.com/redirect-client-to-this-url",
  "sdkToken": null,
  "returnUrl": null
}

GET https://app-api.coinify.com/kyc/identification-attempts/<id>

This endpoint allows you to query a specific identification attempt by ID.

Response
HTTP Response code JSON data
200 OK Success, Identification attempt object as shown to the right.
401 Unauthorized Error, access token missing.
403 Forbidden Error, invalid access token.
404 Not Found Error retrieving identification attempt with the specified ID.

List identification attempts

Example request for GET /kyc/identification-attempts

[
  {
    "id": "0cf12488-0dcf-4f99-96c4-e1ec879c906d",
    "state": "approved",
    "createTime": "2019-11-26T07:45:44.852Z",
    "approveTime": "2019-11-26T07:49:19.126Z",
    "expireTime": "2028-03-04T00:00:00.000Z",
    "redirectUrl": "https://www.example.com/redirect-client-to-this-url",
    "sdkToken": null,
    "returnUrl": null
  },
  {
    // next attempt...
  }
]

GET https://app-api.coinify.com/kyc/identification-attempts

This endpoint allows you to get all identification attempts for the trader.

The identification attempts are ordered by createTime (newest first).

Response
HTTP Response code JSON data
200 OK Success, A list of identification attempt objects as shown to the right.
401 Unauthorized Error, access token missing.
403 Forbidden Error, invalid access token.

Skip identification process (KYC) - Sandbox environment only

For testing purposes, you can skip the identification process in Sandbox environment. You can find a short article on how to do this by following this link.

Address

Provide Address

Example request for POST /traders/me/address

{
    "street": "Best street 1",
    "city": "City",
    "postalCode": "1000"
}

POST https://app-api.coinify.com/traders/me/address

Endpoint to provide address

Request object

The parameters in the request object are as follows:

Parameter Type Default Description
street String Required Residential street
city String Required Residential city
postalCode String null Residential postal code≤
Response
HTTP Response code JSON data
201 Created Success, empty object {}
400 Bad request Error, already provided
401 Unauthorized Error, access token missing.
403 Forbidden Error, invalid access token.

Get Address

Example response for GET /traders/me/address

{
    "street": "Best street 1",
    "city": "City",
    "postalCode": "1000",
    "country": "US",
    "state": "NY"
}

GET https://app-api.coinify.com/traders/me/address

Endpoint to get address

Response
HTTP Response code JSON data
200 OK Success
401 Unauthorized Error, access token missing.
403 Forbidden Error, invalid access token.

Corporate Details

Provide Corporate Details

Example request for POST /traders/me/corporate-details

{
    "name": "Some corporation name",
    "industry": "wholesale_and_retail_trade",
    "website": "www.somecorporation.com",
    "description": "some corporation description"
}

POST https://app-api.coinify.com/traders/me/corporate-details

Endpoint to provide corporate details

Request object

The parameters in the request object are as follows:

Parameter Type Default Description
name String Required Name of the corporation
industry String Required Can be one of these values 'wholesale_and_retail_trade', 'accommodation_and_food_service', 'information_and_communication', 'professional_or_scientific_and_technical_activities', 'administrative_and_support_service_activities', 'education', 'financial_and_insurance_activities', 'human_health', 'real_estate_activities', 'arts_or_entertainment_or_recreation', 'non_profit_and_public_sectors', 'other'
otherIndustry String null Alternative industry, needs to be supplied if 'other' is chosen as industry
website String null Corporate website
description String Required Description of the corporation
Response
HTTP Response code JSON data
201 Created Success, empty object {}
400 Bad request Error, already provided
401 Unauthorized Error, access token missing.
403 Forbidden Error, invalid access token.

Source of funds

Get annual trade volume buckets

Example response for GET /traders/me/source-of-funds/annual-trade-volume-buckets

[
  {
    "id": 1,
    "lt": 10000,
    "currency": "EUR"
  },
  {
    "id": 2,
    "lt": 50000,
    "gte": 10000,
    "currency": "EUR"
  },
  {
    "id": 3,
    "gte": 50000,
    "currency": "EUR"
  }
]

GET https://app-api.coinify.com/traders/me/source-of-funds/annual-trade-volume-buckets

Endpoint to get annual trade volume buckets. ID of the response will be used for next request

Provide source of funds

Example request for POST /traders/me/source-of-funds

{
    "originOfFunds": "crypto-mining",
    "annualTradeVolumeBucketId": 1,
    "areFundsFromLegalActivities": true
}

POST https://app-api.coinify.com/traders/me/source-of-funds

Endpoint to provide source of funds.

Request object

The parameters in the request object are as follows:

Parameter Type Default Description
originOfFunds String Required Must be one of the following values for an individual trader: 'occupation','inheritance','investment-proceeds','crypto-mining','savings', 'loan', 'crypto-purchased-elsewhere', 'sale-of-property', 'sale-of-precious-goods'.
Options for a corporate trader: 'investment-proceeds', 'crypto-mining', 'business-income'.
annualTradeVolumeBucketId Integer Required Id of bucket see get annual trade volume buckets endpoint to get ID.
areFundsFromLegalActivities Boolean Required Must be set to true for a valid request
Response
HTTP Response code JSON data
201 Created Success, empty object {}
400 Bad request Error, already provided
401 Unauthorized Error, access token missing.
403 Forbidden Error, invalid access token.

Shareholder Relations

Provide Shareholder Relation

Example request for POST /traders/me/shareholder-relation

{
    "shareholderRelationState": "not_related"
}

POST https://app-api.coinify.com/traders/me/shareholder-relation

Endpoint to provide shareholder relation state. Its only applicable for corporate traders.

Used for providing whether shareholders in the corporations are related in some way

Request object

The parameters in the request object are as follows:

Parameter Type Default Description
shareholderRelationState String Required Shareholder Relation state can be one of the following: related, not_related
Response
HTTP Response code JSON data
200 OK Success
400 Bad request Error, invalid request
401 Unauthorized Error, access token missing.
403 Forbidden Error, invalid access token.

Politically Exposed Person

Provide PEP (Politically Exposed Person)

Example request for POST /traders/me/pep

{
    "pepState": "pep"
}

POST https://app-api.coinify.com/traders/me/pep

Endpoint to provide PEP state.

Request object

The parameters in the request object are as follows:

Parameter Type Default Description
pepState String Required PEP can be one of these values for individual traders 'pep','non_pep','pep_relative' and these values for corporate traders 'pep','non_pep'
Response
HTTP Response code JSON data
200 OK Success
400 Bad request Error, invalid request
401 Unauthorized Error, access token missing.
403 Forbidden Error, invalid access token.

Resend emails

The following endpoints allow you to resend specific emails that are usually sent to the traders by our system, in case for some reason the trader does not receive the emails in the first place or does not provide the necessary information in time.

Resend Proof of Source of Funds (PSOF) Email

POST https://app-api.coinify.com/traders/me/resend-proof-of-source-of-funds-email

This endpoint enables you to resend a PSOF email to the trader. A PSOF email is usually sent when a trader reaches a significant volume of trades.
If the trader does not complete the PSOF before it expires, you can resend it with a request to this endpoint. You must be authenticated as the specific trader to use this endpoint and for the email to be delivered to the correct address.

HTTP Response code JSON data
200 OK Success
400 Bad request Error, invalid request
401 Unauthorized Error, access token missing.
403 Forbidden Error, invalid access token.

Resend Enhanced Due Diligence (EDD) Email

POST https://app-api.coinify.com/traders/me/resend-enhanced-due-diligence-email

This endpoint enables you to resend an EDD email to the trader. An EDD email is usually sent when the customer is marked as a high-risk trader.
If the trader does not complete the EDD before it expires, you can resend it with a request to this endpoint. You must be authenticated as the specific trader to use this endpoint and for the email to be delivered to the correct address.

HTTP Response code JSON data
200 OK Success
400 Bad request Error, invalid request
401 Unauthorized Error, access token missing.
403 Forbidden Error, invalid access token.

Resend KYB upload email (Corporate traders only)

POST https://app-api.coinify.com/traders/me/resend-kyb-upload-email

This endpoint enables you to resend the KYB document upload email to a corporate trader.
The KYB email is automatically sent when a new corporate trader creates an account and provides the basic mandatory information required from all corporate traders. See mandatory info that a corporate trader needs to provide here.
Trader authentication is required to use this endpoint and for the email to be delivered to the correct address.

HTTP Response code JSON data
200 OK Success
400 Bad request Error, invalid request
401 Unauthorized Error, access token missing.
403 Forbidden Error, invalid access token.

Trades

This section describes the trade object as well as the endpoints that allow you to create, query and manipulate trades:

The following sections describe the trade object and continue to describe each of the endpoints in greater detail.

Trade object

Example awaiting_transfer_in trade object buying 2.41526674 BTC for 1,000.00 USD

{
  "id": 113475347,
  "traderId": 754035,
  "traderEmail": "customer@coinify.com",
  "state": "awaiting_transfer_in",
  "inCurrency": "USD",
  "outCurrency": "BTC",
  "inAmount": 1000.00,
  "outAmountExpected": 2.41526674,
  "transferIn": {
    "currency": "USD",
    "sendAmount": 1000.00,
    "receiveAmount": 1000.00,
    "medium": "card",
    "details": {
      "redirectUrl": "https://provider.com/payment/d3aab081-7c5b-4ddb-b28b-c82cc8642a18"
    }
  },
  "transferOut": {
    "currency": "BTC",
    "medium": "blockchain",
    "sendAmount": 2.41526674,
    "receiveAmount": 2.41526674,
    "details": {
      "account": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
    }
  },
  "quoteExpireTime": "2016-04-01T12:38:19Z",
  "updateTime": "2016-04-01T12:27:36Z",
  "createTime": "2016-04-01T12:23:19Z"
}

Example completed trade object buying 2.41551728 BTC for 1,000.00 USD

{
  "id": 113475347,
  "traderId": 754035,
  "traderEmail": "customer@coinify.com",
  "state": "completed",
  "inCurrency": "USD",
  "outCurrency": "BTC",
  "inAmount": 1000.00,
  "outAmount": 2.41551728,
  "transferIn": {
    "currency": "USD",
    "sendAmount": 1000.00,
    "receiveAmount": 1000.00,
    "medium": "card",
    "details": {
      "redirectUrl": "https://provider.com/payment/d3aab081-7c5b-4ddb-b28b-c82cc8642a18"
    }
  },
  "transferOut": {
    "currency": "BTC",
    "medium": "blockchain",
    "sendAmount": 2.41526674,
    "receiveAmount": 2.41526674,
    "details": {
       // Trader's bitcoin address that has received the 2.41551728 BTC
      "account": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
      // The BTC transaction that sent out the BTC to the above address
      "transaction": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
    }
  },
  "updateTime": "2016-04-01T12:27:36Z",
  "createTime": "2016-04-01T12:23:19Z"
}

Example completed trade object selling 2.41551728 BTC for 1,000.00 USD

{
  "id": 113475348,
  "traderId": 754035,
  "traderEmail": "customer@coinify.com",
  "state": "completed",
  "inCurrency": "BTC",
  "outCurrency": "USD",
  "inAmount": 2.41551728,
  "outAmount": 1000.00,
  "transferIn": {
    "currency": "BTC",
    "sendAmount": 2.41551728,
    "receiveAmount": 2.41551728,
    "medium": "blockchain",
    "details": {
      // Coinify's bitcoin address to where the trader sent the 2.41551728 BTC
      "account": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
      "paymentUri": "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=2.41551728",
      // The BTC transaction that sent out the BTC to the above address
      "transaction": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
    }
  },
  "transferOut": {
    "currency": "USD",
    "sendAmount": 1000.00,
    "receiveAmount": 1000.00,
    "medium": "bank",
    "mediumReceiveAccountId": 12345 // Reference to the traders bank account
  },
  "updateTime": "2016-04-01T12:27:36Z",
  "createTime": "2016-04-01T12:23:19Z"
}

The following table defines the fields of a trade object:

Key Type Description
id Integer Unique ID for this trade
traderId Integer Reference to the trader that created the trade
traderEmail String Email address of trader that created the trade
state Trade state The current state of the trade
inCurrency String Currency (ISO 4217) denominating inAmount.
outCurrency String Currency (ISO 4217) denominating outAmount or outAmountExpected.
inAmount Float The amount of inAmount that this trade covers. Is always positive.
outAmount Float (Optional) The amount of outCurrency that this trade resulted in. Is always positive. NOTE: This field is only defined if the state of the trade is completed. For all other states, see the outAmountExpected field.
outAmountExpected Float The amount of outCurrency that this trade is expected to result in. Is always positive.
transferIn Transfer object Object describing how we (Coinify) will receive/have received the money to fund the trade.
transferOut Transfer object Object describing how we (Coinify) will send/have sent the result of the trade back to the trader.
quoteExpireTime ISO 8601 time (Optional) The time when the price quote underlying this trade expires. NOTE: This field is only defined in the following states: awaiting_transfer_in, processing, reviewing, payment_authorized.
updateTime ISO 8601 time The time when the trade was last updated.
createTime ISO 8601 time Timestamp for when this trade was first created

Trade states

The following table lists all possible states of a trade (the state property):

State Description
awaiting_transfer_in (Starting state) Trade was successfully created, and is waiting for trader’s payment
payment_authorized (Only if transferIn.medium = ‘card’) We have reserved trade.inAmount on customers card. Followed immediately by processing if trader has performed KYC (completed identity verification, provided personal information, and provided source of funds), and the quote is not expired.
processing Trade has received the trader’s payment, and we are processing the trade.
completed (Ending state) Trade completed successfully.
cancelled (Ending state) Trade cancelled.
rejected (Ending state) Trade was rejected.
expired (Ending state) Trade expired before it completed.

The states, as well as the possible transitions between them, are visualized in this diagram:

Trade state diagram

Transfer object

Each trade has two transfer objects - transferIn and transferOut - which contain details about how Coinify will receive money from the trader, and how Coinify will send money back to the trader, respectively.

Each transfer object contains the following keys:

Key Type Description
currency String Currency (ISO 4217) that this transfer is/will be denominated in.
sendAmount Float Amount that is/will be sent to this transfer. Denominated in currency. Is always positive.
receiveAmount Float Amount that this transfer will result in. Denominated in currency. Is always positive and equal to or smaller then sendAmount. The difference between the sendAmount and the receiveAmount will be the fee of the transfer.
medium String Transfer medium. The medium transferring the currency.
mediumReceiveAccountId Integer Reference to our internal accounts. Note at the moment we only have internal bank accounts.
details Medium details Information relevant for this medium

Transfer media

Medium Description
blockchain Blockchain - Transfer on a public blockchain, such as Bitcoin. NOTE: The currency field of the transfer object will determine which blockchain currency.
card Card - Card payment.
bank Bank - Bank account payment. If transfer is incoming we sent our own bank details as the details object. And if transfer is outgoing its the details of the traders bank account.

Blockchain

Details object
Key Type Description
account String Account receiving the money in this transfer (blockchain address).
accountSignature String HMAC-SHA-256 signature signing the account value. Only defined if a shared secret has been established with Coinify.
memo String Optional memo for currencies where memo/destination tag is supported.
transaction String Transaction sending the money in this transfer. Only defined if money has been sent.
refundAccount String Account to refund the money in this transfer in case of failure. Only defined if currency != 'BTC'.
refundAccountSignature String HMAC-SHA-256 signature signing the refundAccount value. Only defined if refundAccount is defined and a shared secret has been established with Coinify.
paymentUri String Payment URI scheme to generate QR code.

Card

Details object

Below, a description of the request/response objects for creating a trade.

Request object
Key Type Description
returnUrl String The return URL to which the user to be sent back after the payment has been created. Can be provided when creating a trade.
Reponse object
Key Type Description
redirectUrl String Source URL to process the payment.
returnUrl String The return URL to which the user to be sent back after the payment has been created.

Bank

Details object
Key Type Description
referenceText String Text that the bank transfer must contain in order for Coinify to correctly register what trade it concerns.
account Object Object with additional information about the bank account.
currency String Currency of the bank account.
type Bank account type Type of the bank account
bic String For sepa and international its the SWIFT / BIC number and for danish accounts its the REG number. Optional in case of domestic bank tansfers.
number String For sepa and international it’s the IBAN (International Bank Account Number). For danish accounts, it’s the BBAN (Basic Bank Account Number). Optional in case of domestic bank transfers.
domesticAccountNumber String Account Number for domestic transfers. (Optional)
regNo String Reg. No. used for some domestic transfers. (Optional)
routingCode String Routing Code used for some domestic transfers. (Optional)
sortCode String Sort Code used for some domestic transfers. (Optional)
bsb String BSB used for some domestic transfers. (Optional)
bank Object Object with additional information about the bank.
name String Name of the bank.
address Object Object with information about the address of the bank.
    →street String Street address.
    →zipcode String Zip/Postal code.
    →city String City.
    →state String State.
    →country String ISO 3166-1 alpha-2 country code
holder Object Object with additional information about the bank account holder.
name String Name of the bank account holder.
address Object Object with information about the address of the account holder.
    →street String Street address.
    →zipcode String Zip/Postal code.
    →city String City.
    →state String State.
    →country String ISO 3166-1 alpha-2 country code

Payment methods

This section contains information about different methods to buy/sell blockchain currencies.

The currently available payment methods are:

Payment method object

The object represents a single payment method, containing the information about how and with what currencies the money transfer can and should be performed, and optionally the fees that are going to be applied to the amounts.

Key Type Description
inMedium String The medium for the in transfer of the trade - by what way the customer pays
outMedium String The medium for the out transfer of the trade - what way the customer receives funds
inCurrencies List The possible currencies in which the incoming amount can be made, optionally filtered by request arguments
outCurrencies List The possible currencies in which the outgoing amount can be made, optionally filtered by request arguments
inFixedFees Object Object of inCurrencies and fixed fees for each currency for the in transfer.
inPercentageFee Float Percentage fee for the in transfer.
minimumInFees Object Minimum fee for the in transfer.
outFixedFees Object Object of outCurrencies and fixed fees for each currency for the out transfer.
outPercentageFee Float Percentage fee for the out transfer.
minimumOutFees Object Minimum fee for the out transfer.
minimumInAmounts Object Object of inCurrencies and the minimum limit for each.
limitInAmounts Object Object of inCurrencies and the trader’s current limit for each currency, based on the current limits of the trader. Note: Only included for authenticated requests.
canTrade Boolean Can this trader create new trades for this payment method? Note: Only included for authenticated requests. If inCurrency, inAmount, outCurrency and outAmount are all provided in request, this value determines if a trade with the specific amounts/currencies can be made. Otherwise, this value determines if any trade can be made with this payment method.
cannotTradeReasons List (Optional) List of reason objects why the trader cannot create new trades (why canTrade is false). Note: Only included for authenticated requests if canTrade is false.
inCurrency String (Optional, if inCurrency parameter provided) Echo of inCurrency parameter.
inAmount Float (Optional, if inAmount parameter provided) Echo of inAmount parameter.
outCurrency String (Optional, if outCurrency parameter provided) Echo of outCurrency parameter.
outAmount Float (Optional, if outAmount parameter provided) Echo of outAmount parameter.

cannotTradeReason object

This object represents a reason for why a trader cannot create a trade with a given payment method. The only field that is always present in the object is the reasonCode. Depending on the value of the reasonCode, other reason-specific fields may be present in the object.

Key Type Description
reasonCode String Machine-readable code for the reason why the trade cannot create a trade. See Possible reasonCodes for a list of possible values
Possible reasonCodes

reasonCode value Description Extra fields
forced_delay Trader must wait until a specific time before creating trade delayEnd (string): ISO-8601 timestamp for when the delay is over.
trade_in_progress Trader must wait until a specific trade has completed tradeId (integer): ID of trade that must be completed
limits_exceeded Creating trade would exceed the trader’s limits None
country_not_supported Trading is not currently supported in the trader’s country. See supported countries for querying a partner’s supported countries. None

List payment methods

Example response for GET /trades/payment-methods (authenticated)

[
    {
      "inMedium": "bank",
      "outMedium": "blockchain",
      "inCurrencies": ["DKK", "EUR", "USD", "GBP", "CHF"],
      "outCurrencies": ["BTC", "ETH", "BSV", "BCH", "XLM", "DASH", "NANO"],
      "inFixedFees": {
        "DKK": 0,
        "EUR": 0,
        "USD": 0,
        "GBP": 0,
        "CHF": 0
      },
      "inPercentageFee": 0.75,
      "minimumInFees": {
        "DKK": 49.00,
        "EUR": 4.99,
        "USD": 4.99,
        "GBP": 4.99,
        "CHF": 4.99
      },
      "outFixedFees": {
        "BTC": 0.0002,
        "ETH": 0,
        "BSV": 0,
        "BCH": 0,
        "XLM": 0,
        "DASH": 0,
        "NANO": 0
      },
      "outPercentageFee": 0,
      "minimumInAmounts": {
        "DKK": 933.38,
        "EUR": 125,
        "USD": 140.17,
        "GBP": 111.35,
        "CHF": 140.08
      },
      "limitInAmounts": {
        "DKK": 7500.86,
        "EUR": 1000.00,
        "USD": 1200.50,
        "GBP": 8000.00,
        "CHF": 800.00
      },
      "canTrade": true
    },
    {
      "inMedium": "blockchain",
      "outMedium": "bank",
      "inCurrencies": ["BTC", "ETH", "BSV", "BCH", "XLM", "DASH", "NANO"],
      "outCurrencies": ["DKK", "EUR", "USD", "GBP", "CHF"],
      "inFixedFees": {
        "BTC": 0,
        "ETH": 0,
        "BSV": 0,
        "BCH": 0,
        "XLM": 0,
        "DASH": 0,
        "NANO": 0
      },
      "inPercentageFee": 0,
      "outFixedFees": {
        "DKK": 40.00,
        "EUR": 5.40,
        "USD": 6.10,
        "GBP": 3.70,
        "CHF": 4.90
      },
      "minimumOutFees": {
        "DKK": 49.00,
        "EUR": 4.99,
        "USD": 4.99,
        "GBP": 4.99,
        "CHF": 4.99
      },
      "outPercentageFee": 1,
      "minimumInAmounts": {
        "BTC": 0.01530498,
        "ETH": 0.514718127075,
        "BSV": 0.65349743,
        "BCH": 0.32362399,
        "XLM": 1077.1290499,
        "DASH": 1.77,
        "NANO": 231.41
      },
      "limitInAmounts": {
        "BTC": 0.1224398,
        "ETH": 4.117745016597,
        "BSV": 5.2279794,
        "BCH": 2.58899193,
        "XLM": 8617.0323993,
        "DASH": 15.2,
        "NANO": 1249.23
      },
      "canTrade": true
    },
    {
      "inMedium": "card",
      "outMedium": "blockchain",
      "inCurrencies": ["DKK", "EUR", "USD", "GBP", "CHF"],
      "outCurrencies": ["BTC", "ETH", "BSV", "BCH", "XLM", "DASH", "NANO"],
      "inFixedFees": {
        "DKK": 0,
        "EUR": 0,
        "USD": 0,
        "GBP": 0,
        "CHF": 0
      },
      "inPercentageFee": 3.5,
      "minimumInFees": {
        "DKK": 49.00,
        "EUR": 4.99,
        "USD": 4.99,
        "GBP": 4.99,
        "CHF": 4.99
      },
      "outFixedFees": {
        "BTC": 0.0002,
        "ETH": 0,
        "BSV": 0,
        "BCH": 0,
        "XLM": 0,
        "DASH": 0,
        "NANO": 0
      },
      "outPercentageFee": 0,
      "minimumInAmounts": {
        "DKK": 149.34,
        "EUR": 20,
        "USD": 22.43,
        "GBP": 17.82,
        "CHF": 22.41
      },
      "limitInAmounts": {
        "DKK": 746.71,
        "EUR": 100,
        "USD": 112.14,
        "GBP": 89.08,
        "CHF": 112.06
      },
      "canTrade": false,
      "cannotTradeReasons": [
        {
          "reasonCode": "forced_delay",
          "delayEnd": "2016-04-01T12:27:36Z"
        }
      ]
    }
]

GET https://app-api.coinify.com/trades/payment-methods

This endpoint returns a list of payment methods objects, available for creating and paying for trades.

If you want know which network/chain does a particular supported cryptocurrency belong to, please find the list here.

Response
HTTP Response code Error code JSON data
200 OK Success, response as shown to the right.
400 Bad request invalid_argument Error, interpreting the request (wrong input values for query parameters).
500 Internal error internal_error Error, an internal error happened.

Request trade price quote

Example request for POST /trades/quote

// This request is saying: "I want to buy BTC for 1,000.00 USD. How much BTC will I get?"
{
  "baseCurrency": "USD",
  "quoteCurrency": "BTC",
  "baseAmount": -1000.00
}

Example response for POST /trades/quote

// This response is saying "We'll give you 2.41551728 BTC for 1,000.00 USD"
{
  "id": 123456,
  "baseCurrency": "USD",
  "quoteCurrency": "BTC",
  "baseAmount": -1000.00,
  "quoteAmount": 2.41551728,
  "issueTime": "2016-04-01T11:47:24Z",
  "expiryTime": "2016-04-01T12:02:24Z"
}

POST https://app-api.coinify.com/trades/quote

Before actually creating a trade, you (as a trader) have the option of getting a price quote (i.e. an offer for a specific exchange rate) that is valid for a short period of time. When creating a trade you can reference a price quote and trade using the rate given in the quote.

Request object

The parameters in the request object are as follows:

Parameter Type Default Description
baseCurrency String Required Relative to what currency (ISO 4217) do you want a price quote? Denominates baseAmount.
quoteCurrency String Required What currency (ISO 4217) do you want the price quote in?
baseAmount Float Required Amount to get a price quote for. Denominated in baseCurrency.
transferIn Object Empty object An object describing how Coinify will receive the money for this transfer.
medium String null Transfer medium
transferOut Object Empty object An object describing how Coinify will send the result of the trade to the trader.
medium String null Transfer medium
Request object examples

The following table exemplifies how baseCurrency and quoteCurrency should be interpreted.

baseCurrency quoteCurrency baseAmount Interpretation
"USD" "BTC" 123.45 I want to buy 123.45 USD for BTC.
"USD" "BTC" -123.45 I want to sell 123.45 USD for BTC.
"BTC" "USD" 1.5 I want to buy 1.5 BTC for USD.
"BTC" "USD" -1.5 I want to sell 1.5 BTC for USD.
Response object

The success response object contains the following fields:

Key Type Description
id Integer Price quote identifier. Pass this identifier to the create trade endpoint to claim the offer. Note: Not included if anonymous (un-authenticated) request.
baseCurrency String Currency (ISO 4217) denominating baseAmount, the known amount.
quoteCurrency String Currency (ISO 4217) denominating quoteAmount, the unknown amount.
baseAmount Float How much of baseCurrency does this quote apply for?
quoteAmount Float How much of quoteCurrency is this quote? This is the “result”, the price quote.
transferIn Object transferIn object, only returned if provided in request
medium String Transfer medium
feeAmount Float Transfer fee (Will be added on the trade)
currency String Currency
transferOut Object transferOut object, only returned if provided in request
medium String Transfer medium
feeAmount Float Transfer fee (Will be added on the trade)
currency String Currency
issueTime ISO 8601 time Timestamp for when this price quote was issued.
expiryTime ISO 8601 time Timestamp for when this price quote expires.
Response
HTTP Response code JSON data
201 Created Success, A Price quote created and returned object as shown to the right
401 Unauthorized Error, access token missing.

Get price quote details

Example response (buy) for GET /trades/quote/:id/details?transferInMedium=card&transferOutMedium=blockchain

{
  "transferIn": {
    "currency": "EUR",
    "isMinimumHandlingFeeApplied": false,
    "isMaximumHandlingFeeApplied": false,
    "sendAmount": 103.25,
    "receiveAmount": 100,
    "totalFee": 3.25,
    "partnerFee": 3, // Not present if partner fee is not enabled
    "handlingFee": 0.25
  },
  "transferOut": {
    "currency": "ETH",
    "sendAmount": 0.009582,
    "receiveAmount": 0.008582,
    "networkFee": 0.001 // Not present if no network fee
  },
  "exchangeRate": 10436.23
}

Example response (buy) for GET /trades/quote/:id/details?transferInMedium=card&transferOutMedium=blockchain&promoCode=12314code4321

{
  "transferIn": {
    "currency": "EUR",
    "isMinimumHandlingFeeApplied": false,
    "isMaximumHandlingFeeApplied": false,
    "sendAmount": 103.25,
    "receiveAmount": 100,
    "totalFee": 3.25,
    "partnerFee": 3, // Not present if partner fee is not enabled
    "handlingFee": 0.25
  },
  "transferOut": {
    "currency": "ETH",
    "sendAmount": 0.009582,
    "receiveAmount": 0.008582,
    "networkFee": 0.001, // Not present if no network fee
    "promoCodeValue": {
      "amount": 10,
      "currency": "EUR"
    },
    "promoCodeRedeemedAmount": {
      "amount": 0.001,
      "currency": "ETH"
    }
  },
  "exchangeRate": 10436.23
}

Example response (sell) for GET /trades/quote/:id/details?transferInMedium=blockchain&transferOutMedium=bank

{
  "transferIn": {
    "currency": "BTC",
    "sendAmount": 0.009582,
    "receiveAmount": 0.009582
  },
  "transferOut": {
    "currency": "EUR",
    "isMinimumHandlingFeeApplied": false,
    "isMaximumHandlingFeeApplied": false,
    "sendAmount": 103.25,
    "receiveAmount": 100,
    "totalFee": 3.25,
    "partnerFee": 3, // Not present if partner fee is not enabled
    "handlingFee": 0.25
  },
  "exchangeRate": 10775.41
}

GET https://app-api.coinify.com/trades/quote/:id/details

Endpoint should be used to get an overview of fees and amounts, before creating the trade

Request

The parameters in the request object are as follows:

Parameter Type Default Description
transferInMedium String Required Transfer medium
transferOutMedium String Required Transfer medium
promoCode String Optional Used to validate if promo quote is valid and get new amounts

Response (buy)

Key Type Description
transferIn Object
currency String
sendAmount Number The amount the trader has to pay
receiveAmount Number The amount we receive without the fees
handlingFee Number The fee we charge for the payment
partnerFee Number Partner fee (only present if enabled for partner)
totalFee Number Handling fee + partner fee, same as the difference between sendAmount and receiveAmount
isMinimumHandlingFeeApplied Boolean True if handling fee is the same as the minimum fee
isMaximumHandlingFeeApplied Boolean True if handling fee is the same as the maximum fee
transferOut Object
currency String
sendAmount Number The amount before network fee
receiveAmount Number The amount the trader receives
networkFee Number Network fee for the transaction
promoCodeValue Object {amount, currency} of the promo code value in FIAT (only present if promoCode is provided)
promoCodeRedeemedAmount Object {amount, currency} of the promo code value in Crypto (only present if promoCode is provided)
exchangeRate Number Exchange rate used for this trade

Response (sell)

Key Type Description
transferIn Object
currency String
sendAmount Number The amount the trader has to pay
receiveAmount Number The amount we receive without the fees
transferOut Object
currency String
sendAmount Number The amount we send without the fee
receiveAmount Number The amount the trader receives after fees are subtracted
handlingFee Number The fee we charge for the payout
partnerFee Number Partner fee (only present if enabled for partner)
totalFee Number Handling fee + partner fee, same as the difference between sendAmount and receiveAmount
isMinimumHandlingFeeApplied Boolean True if handling fee is the same as the minimum fee
isMaximumHandlingFeeApplied Boolean True if handling fee is the same as the maximum fee
exchangeRate Number Exchange rate used for this trade

Create trade

Example request for POST /trades, using a valid price quote

{
  "priceQuoteId": 123456,
  "transferIn": {
    "medium": "card"
  },
  "transferOut": {
    "medium": "blockchain",
    "details": {
      "account": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
    }
  },
  "partnerContext": {
    "clientId": 123,
    "refId": 12
  }
}

Example response for the above request

{
  "id": 113475347,
  "traderId": 754035,
  "traderEmail": "customer@coinify.com",
  "state": "awaiting_transfer_in",
  "inCurrency": "USD",
  "outCurrency": "BTC",
  "inAmount": 1000.00,
  "outAmountExpected": 2.41526674,
  "transferIn": {
    "currency": "USD",
    "sendAmount": 1000.00,
    "receiveAmount": 1000.00,
    "medium": "card",
    "details": {
      "redirectUrl": "https://provider.com/payment/d3aab081-7c5b-4ddb-b28b-c82cc8642a18"
    }
  },
  "transferOut": {
    "currency": "BTC",
    "medium": "blockchain",
    "sendAmount": 2.41526674,
    "receiveAmount": 2.41526674,
    "details": {
      // Trader's bitcoin address that will receive approx. 2.41526674 BTC if trade completes
      "account": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
    }
  },
  "quoteExpireTime": "2016-04-01T12:38:19Z",
  "updateTime": "2016-04-01T12:27:36Z",
  "createTime": "2016-04-01T12:23:19Z"
}

Example request for POST /trades (buy bitcoins for bank transfer)

{
  "priceQuoteId": 123456,
  "transferIn": {
    "medium": "bank"
  },
  "transferOut": {
    "medium": "blockchain",
    "details": {
      "account": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
    }
  }
}

Example response for the above request

{
  "id": 113475347,
  "traderId": 754035,
  "traderEmail": "customer@coinify.com",
  "state": "awaiting_transfer_in",
  "inCurrency": "USD",
  "outCurrency": "BTC",
  "inAmount": 1000.00,
  "outAmountExpected": 2.41526674,
  "transferIn": {
    "currency": "USD",
    "sendAmount": 1000.00,
    "receiveAmount": 1000.00,
    "medium": "bank",
    "details": { // Information about where the user should sent the money
       "account": {
         "currency": "DKK", // Currency of the bank account
         "bic": "HIASDMIASD", // Account bic/swift/reg number depending on the type
         "number": "1234-34235-3324-2342" // Account number
       },
       "bank": {
         "name": "Bank name", // Name of the bank
         "address": { // Address of the bank
           "street": "123 Example Street",
           "zipcode": "12345",
           "city": "Exampleville",
           "state": "CA",
           "country": "US"
         }
       },
       "holder": {
         "name": "John Doe", // Name of the account holder
         "address": { // Address of the account holder
           "street": "123 Example Street",
           "zipcode": "12345",
           "city": "Exampleville",
           "state": "CA",
           "country": "US"
         }
       }
    }
  },
  "transferOut": {
    "currency": "BTC",
    "medium": "blockchain",
    "sendAmount": 2.41526674,
    "receiveAmount": 2.41526674,
    "details": {
      // Trader's bitcoin address that will receive approx. 2.41526674 BTC if trade completes
      "account": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
    }
  },
  "quoteExpireTime": "2016-04-01T12:38:19Z",
  "updateTime": "2016-04-01T12:27:36Z",
  "createTime": "2016-04-01T12:23:19Z"
}

POST https://app-api.coinify.com/trades

The Create trade endpoint allows a trader to trade crypto for fiat, or vice-versa.

On success, the endpoint returns the newly created trade object.

The endpoint supports the following directions:

Request object

The parameters in the request object are as follows:

Parameter Type Default Description
priceQuoteId Integer Required Identifier of valid price quote to base the trade on. Passing an invalid price quote will result in an error.
transferIn Object Required An object describing how Coinify will receive the money for this transfer.
medium String Required Transfer medium.
details Object Optional Can be used to supply returnUrl for medium=card. Transfer details.
transferOut Object Required An object describing how Coinify will send the result of the trade to the trader.
medium String Required Transfer medium.
mediumReceiveAccountId Integer Required if medium is bank Bank account of the trader.
details Object Required if medium is blockchain Transfer details
partnerContext JSON Optional Partner context which will be returned in trade webhooks. Can be one of object, array, string, number, true, false as defined here https://www.json.org/json-en.html
HTTP Response code JSON data
201 Created Success, object returned, as shown to the right
401 Unauthorized Error, access token missing.

List trades

Example response for GET /trades

[
  {
    "id": 113475347,
    "traderId": 754035,
    "traderEmail": "customer@coinify.com",
    "state": "awaiting_transfer_in",
    "inCurrency": "USD",
    "outCurrency": "BTC",
    "inAmount": 1000.00,
    "outAmountExpected": 2.41526674,
    "transferIn": {
      "currency": "USD",
      "sendAmount": 1000.00,
      "receiveAmount": 1000.00,
      "medium": "card",
      "details": {
        "redirectUrl": "https://provider.com/payment/d3aab081-7c5b-4ddb-b28b-c82cc8642a18"
      }
    },
    "transferOut": {
      "currency": "BTC",
      "medium": "blockchain",
      "sendAmount": 2.41526674,
      "receiveAmount": 2.41526674,
      "details": {
        // Trader's bitcoin address that will receive approx. 2.41526674 BTC if trade completes
        "account": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
      }
    },
    "quoteExpireTime": "2016-04-01T12:38:19Z",
    "updateTime": "2016-04-01T12:27:36Z",
    "createTime": "2016-04-01T12:23:19Z"
  },
  {
    // Next trade...
  }
]

GET https://app-api.coinify.com/trades

Response

This endpoint lists all trade objects belonging to the authorized trader.

The trades are ordered by id (highest first).

HTTP Response code JSON data
200 OK Success, response as shown to the right.
400 Bad request Error interpreting the request.
404 Not found Error, trades that belong to the provided trader were not found.
Query parameters
Parameter Type Description
limit String Limit used for pagination (default: 100, max: 100)
offset String Offset used for pagination (default: 0)
trade_state String Return only trades in specific state(s) (optional: will include all states by default)

Get trade information

Example response for GET /trades/113475347

{
  "id": 113475347,
  "traderId": 754035,
  "traderEmail": "customer@coinify.com",
  "state": "awaiting_transfer_in",
  "inCurrency": "USD",
  "outCurrency": "BTC",
  "inAmount": 1000.00,
  "outAmountExpected": 2.41526674,
  "transferIn": {
    "currency": "USD",
    "sendAmount": 1000.00,
    "receiveAmount": 1000.00,
    "medium": "card",
    "details": {
      "redirectUrl": "https://provider.com/payment/d3aab081-7c5b-4ddb-b28b-c82cc8642a18"
    }
  },
  "transferOut": {
    "currency": "BTC",
    "medium": "blockchain",
    "sendAmount": 2.41526674,
    "receiveAmount": 2.41526674,
    "details": {
      // Trader's bitcoin address that will receive approx. 2.41526674 BTC if trade completes
      "account": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
    }
  },
  "quoteExpireTime": "2016-04-01T12:38:19Z",
  "updateTime": "2016-04-01T12:27:36Z",
  "createTime": "2016-04-01T12:23:19Z"
}

GET https://app-api.coinify.com/trades/<id>

This endpoint returns a trade object for the trade with ID <id>.

Response

See trade object.

HTTP Response code JSON data
200 OK Success, response as shown to the right.
400 Bad request Error interpreting the request.
404 Not found Error, trade is not found (no trade with such ID exists or no trade with such ID that belongs to the provided trader).

Cancel trade

Example response for PATCH /trades/113475347/cancel

{
  "id": 113475347,
  "traderId": 754035,
  "traderEmail": "customer@coinify.com",
  // State is now "cancelled"
  "state": "cancelled",
  "inCurrency": "USD",
  "outCurrency": "BTC",
  "inAmount": 1000.00,
  "outAmountExpected": 2.41526674,
  "transferIn": {
    "currency": "USD",
    "sendAmount": 1000.00,
    "receiveAmount": 1000.00,
    "medium": "card",
    "details": {
      "redirectUrl": "https://provider.com/payment/d3aab081-7c5b-4ddb-b28b-c82cc8642a18"
    }
  },
  "transferOut": {
    "currency": "BTC",
    "medium": "blockchain",
    "sendAmount": 2.41526674,
    "receiveAmount": 2.41526674,
    "details": {
      "account": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
    }
  },
  "updateTime": "2016-04-01T12:34:37Z",
  "createTime": "2016-04-01T12:23:19Z"
}

PATCH https://app-api.coinify.com/trades/<id>/cancel

This endpoint cancels the trade in the awaiting_transfer_in state and returns a trade object for the cancelled trade.

Request object

This endpoint requires no request body

Response

See trade object.

HTTP Response code JSON data
200 OK Success, response as shown to the right.
400 Bad request Error trade not cancellable.
404 Not found Error, trade is not found (no trade with such ID exists or no trade with such ID that belongs to the provided trader).

Testing

This section explains how you can complete trades that you create in the Sandbox environment.

Before being able to complete the trades, make sure you have succesfully completed these actions and provided the following mandatory trader information:

This section contains the following endpoints that allow you to test the full end-to-end trade flow:

Force complete trade

Example request for POST /trades/113475347/test/complete-trade

{
  "details": {
    "account": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
  },
  "amount": 200.00, // Transfer out amount of the fake transaction
  "currency": "EUR" // Transfer out currency of the amount
}

POST https://app-api.sandbox.coinify.com/trades/<id>/test/complete-trade

This endpoint will complete a newly created trade which is in the awaiting_transfer_in state. Provide the ID of the trade in the <id> path parameter to complete the specific.

Request object

The parameters in the request object are optional and are use to validate the trade if they are sent:

Parameter Type Default Description
amount Float (Optional) Transfer out receive amount of the trade.
currency String (Optional) Currency of the amount.
details Medium details (Optional) Information relevant for this medium
Details object

Response

This endpoint returns 204 No Content if the trade is completed successfully.

HTTP Response code JSON data
204 No Content Success, empty response.
400 Bad request Error interpreting the request.
500 Server error Error error has occurred parsing the data.

Complete trade with a test Card payment

Use the following test cards to complete a Buy trade via card payment.

Card Number Name on card Expected Result
4000027891380961 FL-BRW1 Card payment Successful
4000027891380961 CL-BRW1 Challenge flow (3DS)
4000164166749263 FL-BRW1 Do not honour
5001638548736201 FL-BRW1 Declined
4000020951595032 ERR-BRW1 Error

Complete trade with a fake bank transaction

Example request for POST /trades/113475347/test/bank-transfer

{
  "sendAmount": 200.00, // Amount of the fake transaction
  "currency": "EUR" // Currency of the amount
}

POST https://app-api.sandbox.coinify.com/trades/<id>/test/bank-transfer

This endpoint creates a fake bank transfer for the trade with ID <id> and processes the trade as if a real bank transfer had occurred.

Request object

The parameters in the request object are as follows:

Parameter Type Default Description
sendAmount Integer Required Amount sent from the trader’s bank account. This corresponds to the transferIn.sendAmount of the trade.
currency String Required Currency of the amount.

Response

This endpoint returns 204 No Content if the fake bank transfer creation succeeded.

HTTP Response code JSON data
204 No Content Success, empty response.
400 Bad request Error interpreting the request.
500 Server error Error error has occurred parsing the data.

Complete/Skip KYC

In the sandbox environment it is possible to automatically approve an identification-attempt by including state: 'approved' in the request object. See example on the right or follow a more detailed explanation in this article.

Example of request to approve identification attempt

{
  "returnUrl": "https://mypage.com/ida_complete",
  "state": "approved"
}

Bank accounts

This section contains the create, list, get and delete end-points of the bank accounts used when creating a trade and to make payouts:

Bank account object

Example of a bank account object:

{
  "id": 1234,
  "account": {
    "name": "my account",
    "type": "danish", // Type of bank account
    "currency": "DKK", // Currency of the bank account
    "bic": "HIASDMIASD", // Account bic/swift/reg number depending on the type
    "number": "1234-34235-3324-2342" // Account number
  },
  "bank": {
    "address": {
      "country": "US"
    }
  },
  "holder": {
    "name": "John Doe", // Name of the account holder
    "address": { // Address of the account holder
      "street": "123 Example Street",
      "zipcode": "12345",
      "city": "Exampleville",
      "state": "CA",
      "country": "US"
    }
  },
  "update_time": "2016-04-01T12:27:36Z",
  "create_time": "2016-04-01T12:23:19Z"
}
Key Type Description
id Integer Identifier for the bank account
account Object Object with additional information about the bank account.
name String Optional name for the trader to identify the account.
type bank account type Type of the bank account.
currency String Currency of the bank account
bic String For sepa and international its the SWIFT / BIC number and for danish accounts its the REG number.
number String For sepa and international it’s the IBAN (International Bank Account Number). For danish accounts, it’s the BBAN (Basic Bank Account Number).
bank Object Object with additional information about the bank.
address Object Object with information about the address of the bank.
    →country String ISO 3166-1 alpha-2 country code
    →state String State (Required for country US).
holder Object Object with additional information about the bank account holder.
name String Name of the bank account holder.
address Object Object with information about the address of the account holder.
    →street String Street address.
    →zipcode String Zip/Postal code.
    →city String City.
    →state String State. (Required for country US)
    →country String ISO 3166-1 alpha-2 country code
updateTime ISO 8601 time The time when the bank account was last updated.
createTime ISO 8601 time Timestamp for when this bank account was first created.

Bank account types

The bank accounts have three different types depending on the account currency (account.currency) and the bank country (bank.address.country).

When creating a new bank account, you don’t explicitly specify the type; instead Coinify decides the type for you, based on the requirements in the following table:

Type Description Requirements
danish Danish national bank account  Country must be DK and currency DKK.
sepa SEPA bank account Country must be in our list of SEPA countries, and currency must be EUR.
international International bank account Everything else.

Create bank account

Example request to POST /bank-accounts

{
  "account": {
    "name": "my bank account",
    "currency": "DKK", // Currency of the bank account
    "bic": "6456", // Account REG number
    "number": "12345435345345" // Account number
  },
  "bank": {
    "address": { // Address of the bank
      "country": "DK"
    }
  },
  "holder": {
    "name": "John Doe", // Name of the account holder
    "address": { // Address of the account holder
      "street": "123 Example Street",
      "zipcode": "12345",
      "city": "Exampleville",
      "state": "CA",
      "country": "US"
    }
  }
}

Example response for the above request

{
  "id": 12345, // Identifier of the bank account
  "account": {
    "name": "my bank account",
    "type": "danish", // Type of bank account
    "currency": "DKK", // Currency of the bank account
    "bic": "6456", // Account bic/swift/reg number depending on the type
    "number": "12345435345345" // Account number
  },
  "bank": {
    "address": { // Address of the bank
      "country": "DK"
    }
  },
  "holder": {
    "name": "John Doe", // Name of the account holder
    "address": { // Address of the account holder
      "street": "123 Example Street",
      "zipcode": "12345",
      "city": "Exampleville",
      "state": "CA",
      "country": "US"
    }
  },
  "update_time": "2016-04-01T12:27:36Z",
  "create_time": "2016-04-01T12:23:19Z"
}

POST https://app-api.coinify.com/bank-accounts

On success, the endpoint returns the newly created bank-account object.

Some of the fields are only required if the bank account is of type international see account-types.

How to create a bank account in Sandbox

To create a bank account in Sandbox you can use the fake account details from the example request body on the right. You can also create a fake bank account with your own preferred details. However, bank accounts in sandbox must follow the basic bank account rules from the real world for a particular country. Follow these guidelines to create your own fake bank account in sandbox:

You can easily look up the SWIFT/BIC bank codes and IBAN basic structure for a specific country online.

Request object

The parameters in the request object are as follows:

Parameter Type Default Description
account Object Required  Object with additional information about the bank account.
name String Optional Optional name for the trader to identify the account.
currency String Required Currency of the bank account.
bic String Required For sepa and international it’s the SWIFT / BIC number and for danish accounts it’s the REG number.
number String Required For sepa and international it’s the IBAN (International Bank Account Number). For danish accounts, it’s the BBAN (Basic Bank Account Number).
bank Object Required for non-SEPA payments Object with additional information about the bank.
address Object Required Object with information about the address of bank.
    →country String Required ISO 3166-1 alpha-2 country code
    →state String State (Required for country US).
holder Object Required Object with additional information about the bank account holder.
name String Required Name of the bank account holder.
address Object Required Object with information about the address of the account holder.
    →street String Required Street address.
    →zipcode String Optional Zip/Postal code.
    →city String Required City.
    →state String Required State. Required for country US
    →country String Required ISO 3166-1 alpha-2 country code.
HTTP Response code JSON data
201 Created Success, response as shown to the right.
400 Bad request Error, interpreting the request (missing parameters).
401 Unauthorized Error, access token missing.
HTTP status code Error code  Description
400 missing_argument There was something wrong with the signup request. See the error_description argument for a specific, human-readable error message.
400 invalid_argument There was something wrong with the signup request. See the error_description argument for a specific, human-readable error message.
400 invalid_iban Provided account.number is an invalid IBAN. This is only for SEPA countries.

List bank accounts

Example request for GET /bank-accounts

[
    {
      "id": 12345, // Identifier of the bank account
      "account": {
        "type": "danish", // Type of bank account
        "currency": "DKK", // Currency of the bank account
        "bic": "6456", // Account bic/swift/reg number depending on the type
        "number": "12345435345345" // Account number
      },
      "bank": {
        "address": { // Address of the bank
          "country": "DK"
        }
      },
      "holder": {
        "name": "John Doe", // Name of the account holder
        "address": { // Address of the account holder
          "street": "123 Example Street",
          "zipcode": "12345",
          "city": "Exampleville",
          "state": "CA",
          "country": "US"
        }
      },
      "update_time": "2016-04-01T12:27:36Z",
      "create_time": "2016-04-01T12:23:19Z"
    },
    {
      // Next bank-account...
    }
]

GET https://app-api.coinify.com/bank-accounts

On success, the endpoint returns a list of bank-account objects for the current trader.

The accounts are ordered by id (highest first).

HTTP Response code JSON data
200 OK Success, response as shown to the right.
401 Unauthorized Error, access token missing.
404 Not found Error, no bank accounts found.

Get bank account

Example request for GET /bank-accounts/12345

{
  "id": 12345, // Identifier of the bank account
  "account": {
    "type": "danish", // Type of bank account
    "currency": "DKK", // Currency of the bank account
    "bic": "6456", // Account bic/swift/reg number depending on the type
    "number": "12345435345345" // Account number
  },
  "bank": {
    "address": { // Address of the bank
      "country": "DK"
    }
  },
  "holder": {
    "name": "John Doe", // Name of the account holder
    "address": { // Address of the account holder
      "street": "123 Example Street",
      "zipcode": "12345",
      "city": "Exampleville",
      "state": "CA",
      "country": "US"
    }
  },
  "update_time": "2016-04-01T12:27:36Z",
  "create_time": "2016-04-01T12:23:19Z"
}

GET https://app-api.coinify.com/bank-accounts/<id>

On success, the endpoint returns a bank-account object.

HTTP Response code JSON data
200 OK Success, response as shown to the right.
401 Unauthorized Error, access token missing.
403 Forbidden Error, the supplied access token doesn’t have the rights to delete this bank-account.
404 Not found Error, bank-account is not found.

Delete bank account

DELETE https://app-api.coinify.com/bank-accounts/<id>

Deletes a bank account.

HTTP Response code Description
204 No Content Success, empty response.
401 Unauthorized access token missing.
403 Forbidden The supplied access token doesn’t have the rights to delete this bank-account.
404 Not found bank-account is not found.

Payment page

The payment page is the default go-to solution when paying for a trade with a credit/debit card - It easy to integrate, it works on web, mobile web and native mobile and covers the common use-cases to let your customers pay for crypto currencies.

The user journey of the payment page is built with safety and simplicity in mind. Your app opens the payment page in an iframe, webview or browser tab. The user inputs his credit card details and our services authenticates the payment or denies the transaction. The entire payment flow takes a 2-3 minutes for the user to complete.

Payment page

How to use

When you create a trade with the input medium set to card, a redirectUrl will be returned which needs to be opened up inside an iframe.

Payment page sequence diagram

Return URL

It is crucial that during trade creation you provide a returnURL the payment page will redirect itself to upon successful or denied payment attempt.

E.g. supplied returnURL = https://mywallet.com/payment-return

Payment result Return URL
Success https://mywallet.com/payment-return?state=success
Failure https://mywallet.com/payment-return?state=failed

When this option is used it will be up to you to implement and host a relevant way of handling the return that makes sense for the given application.

Webhooks

This section describes the webhooks that are sent to partners’ backend systems when certain events occur to entities controlled by the partner.

Webhooks perform signed HTTP POST requests about specific events to a URL of your choice.

You can find the list of IP addresses from which Coinify’s system sends webhook events here: IP address list

Webhook structure

This section describes the general structure of the webhooks that you will receive.

// Example webhook payload
{
  "id": "bd21c0e7-ddb6-4f8e-9367-a6ca00eca25c",
  "time": "2017-09-14T09:07:11.335Z",
  "event": "identification-attempt.approved",
  "context": {
    "traderId": "420"
  }
}

All webhooks are sent as JSON, and shares the same general structure as described in the following table:

Key Type Description
id String (UUID v4) Unique identifier for the event. Retries for the same events will share the same uuid.
time String (ISO-8601 timestamp) Timestamp for when the event has occurred.
event String Event that occurred. See Webhook events for a list of possible events.
context Object Context for this event. Structure is defined by the event as described in [Webhook events].(#webhook-events).

Webhook events

This section provides a list of possible events that can be received as webhooks

Context of identification-attempt.* events

{
  "traderId": "1",
  "rejectReason": "DENIED" // Only for rejected event
}

Context of trade events (trade.rejected, trade.cancelled, trade.expired)

{
  "id": "1235",
  "traderId": "123",
  "residenceCountry": "DK",
  "partnerContext": {
    "clientId": 123,
    "refId": 12
  }
}

Context of trade events (trade.created, trade.transfer-in-completed, trade.completed)

{
  "id": "1235",
  "traderId": "123",
  "residenceCountry": "DK",
  "eurAmount": 100,
  "transferIn": {
    "medium": "card",
    "amount": {
      "amount": 103,
      "currency": "EUR"
    },
    "totalFee": {
      "amount": 3,
      "currency": "EUR"
    }
  },
  "transferOut": {
    "medium": "blockchain",
    "amount": {
      "amount": 0.099,
      "currency": "BTC",
      "isApproximate": false // If this is set, we cannot guarantee the amount or fee
    },
    "totalFee": {
      "amount": 0.0001,
      "currency": "BTC"
    },
    "details": {
      "address": "btc-address",
      "transaction": "btc-tx-id"
    }
  },
  "partnerContext": {
    "clientId": 123,
    "refId": 12
  }
}

Context of otc-trade.completed event

{
  "id": "1234-1234",
  "time": "yyyy-mm-ddThh:mm:ss.msZ",
  "event": "otc-trade.completed",
  "context": {
    "traderId": "123",
    "residenceCountry": "DK",
    "id": "1234",
    "partnerFee": {
      "amount": {
        "amount": 100.00,
        "currency": "EUR"
      }
    },
    "totalFee": {
      "amount": {
        "amount": 200.00,
        "currency": "EUR"
      }
    },
    "transferIn": {
      "amount": {
        "amount": 30000,
        "currency": "EUR"
      },
      "details": {
        "address": "abcd",
        "transaction": "efgh"
      },
      "transactionTime": "yyyy-mm-ddThh:mm:ss.msZ"
    },
    "transferOut": {
      "amount": {
        "amount": 1.000,
        "currency": "BTC"
      },
      "details": {
        "address": "ijkl",
        "transaction": "mnop"
      },
      "transactionTime": "yyyy-mm-ddThh:mm:ss.msZ"
    },
    "createTime": "yyyy-mm-ddThh:mm:ss.msZ"
  }
}
Event Description
identification-attempt.approved Identification attempt has been approved
identification-attempt.rejected Identification attempt has been rejected. See Identification attempt rejection reason for possible reject reasons.
trade.created Trade has been created
trade.completed Trade has been completed
trade.transfer-in-completed Trade transfer in completed and waiting for transfer out to be sent
trade.cancelled Trade has been cancelled
trade.expired Trade has been expired
trade.rejected Trade has been rejected
otc-trade.completed OTC trade has been completed. The only event possible for OTC trades.

How to respond to a webhook request

This section describes how your system should respond to an incoming webhook request

If you respond with a 2xx code, our system will consider the webhook as successfully sent and received.

If you respond with another status code than 2xx, our system will consider the webhook request as a failure and retry at a later time. See Retrying failed requests for more information about the retry strategy.

If the webhook signature is incorrect, you should consider replying just as you would if the signature was correct, to avoid attackers being able to brute-force the shared secret.

Retrying failed requests

This section describes how failed webhook requests will be retried

We will use exponential backoff for handling webhook retries.

Retry attempts Retry Interval
16 16 sec

And use this formula to calculate next retry attempt, which will result in last attempt after ~6 days from first failure.

next_retry_attempt = retry_interval * 2^retry_count

Webhook signature

Node pseudo-code to validate signature

const crypto = require('crypto');

const sharedSecret = 'shared-secret';

// Express example
const body = req.body;
const signature = req.headers['X-Coinify-Webhook-Signature'];

const hash = crypto.createHmac('sha256', sharedSecret)
  .update(JSON.stringify(body))
  .digest('hex');

return hash === signature;

Python pseudo-code to validate signature

import hashlib, hmac

shared_secret = 'the_shared_secret'

# Get the raw HTTP POST body (JSON object encoded as a string)
body = get_body()

# Get the signature from the HTTP or email headers
signature = get_header("X-Coinify-Webhook-Signature")

expected_signature = hmac.new(shared_secret, msg=body, digestmod=hashlib.sha256).hexdigest()
return signature == expected_signature

All webhooks sent from Coinify are signed with a shared secret that is known only by you and Coinify. This ensures the integrity of the data contained in the webhook, and also proves that Coinify is the sender of the webhook (provided the shared secret is not known by anyone else).

Specifically, the signature uses HMAC-SHA256, using the shared secret as the key and the full HTTP request body (UTF-8 encoded) as the message. The resulting signature is provided in lowercase hexadecimal format in the X-Coinify-Webhook-Signature HTTP header.

Signature example

Use the following example to test that your signature validation function is working correctly

Shared secret: my-shared-secret, Payload: {"examplePayload":true}

Expected signature: bcdbb89e3031905f3cc1a20d16b5f969a17a7d8fa0c26e4a807c2193402d66f4

Websocket

This section describes the WebSocket service that can be used to push real-time notifications directly to the browsers of traders.

Overview

Example JavaScript code for connecting and authenticating as trader to WebSockets

const authenticationToken = 'valid-authentication-token-without-Bearer-prefix';
const url = 'wss://app-ws.coinify.com/ws/trader';
const websocket = new WebSocket(url);
websocket.onopen = event => {
  console.log({type: 'open', event});

  // You must send this message immediately after connection opened
  // as well as for each new authenticationToken obtained
  websocket.send(JSON.stringify({type: 'authenticate', token: authenticationToken}));
};
websocket.onerror = event => {
  console.log({type: 'error', event});
};
websocket.onclose = event => {
  console.log({type: 'close', event});
};
websocket.onmessage = event => {
  console.log({type: 'message', event});

  const message = JSON.parse(event.data);
  if (message.type === 'authenticated') {
    const { traderId } = message;
    console.log('Authenticated as trader #' + traderId);
  } else if (message.type === 'event') {
    const { event, context } = message;
    console.log('Received event ' + event + ' with the following context:');
    console.log(context);
  }
};

The WebSocket service is available at wss://app-ws.coinify.com/ws/trader.

To the right you can see a basic example of how to connect and authenticate to the WebSocket service. The example is not complete - e.g. it does not ping the service at regular intervals as described in pinging section.

Authentication

Example message authenticating the connection

{
  "type": "authenticate",
  "token": "<access token>"
}

Response on valid authentication

{
  "type": "authenticated",
  "traderId": "12345"
}

Once connected, the connection must immediately be authenticated with an access token as described in the authentication section in the beginning of the API documentation. The connection will be closed by the service with status code 3109 after 30 seconds if the client fails to authenticate as a trader.

Furthermore, the connection must also be re-authenticated before the currently used access token expires. Connection closes automatically when authentication token expires with status code 3130 if you don’t re-authenticate before.

On every successful authentication, the server responds with a message with type: 'authenticated' as seen on the right.

Message format

All messages sent back and forth are strings containing JSON objects. JSON objects sent to the server must contain a type field, which must contain one of the following values:

Client-to-server message types

type Description
authenticate Authenticates as a trader. Object must also hold a token field, containing an access token. See authentication section above for more information.
ping Pings the server in order to keep the connection alive. See pinging section below.

Furthermore, the server can send messages of the following types:

Server-to-client message types

type Description
authenticated Your connection was successfully authenticated. See authentication section above for more information.
pong Response to a ping message from the client. See pinging section below.
event An event related to the authenticated trader occurred. The message will also contain an event field and possibly other fields as well. See trader events section below.

Pinging

Example ping message

{
  "type": "ping"
}

Response on ping

{
  "type": "pong"
}

Close status codes

Closing a WebSocket connection requires the server to give a numeric status code (See also RFC 6455: 7.4. Status Codes).

The following table shows all possible status codes (in addition to those defined in RFC 6455):

Code Reason
30xx Message error
3000 Message received containing invalid JSON
3005 Message object received without type field
31xx Authentication failure
3101 Message object with type: 'authenticate' received without token field
3109 Failure to authenticate within time limit
3110 Invalid authentication token
3120 Illegal re-authentication by different trader
3130 Authentication token expired
3140 Client tried to ping without being authenticated

In addition to the status code, a JSON-encoded reason object is also returned containing two fields:

Field Description
error Machine-readable error string. Each status code has a distinct error code, which contains a slightly more informational message than just the numeric code.
error_description Human-readable error string

Trader events

This section lists the trader-related events that can be received through our WebSocket service

email-verified

Example email-verified event

{
  "type": "event",
  "event": "email-verified"
}

The trader verified their email address by clicking the link in the verification email.

identification-attempt.approved

Example identification-attempt.approved event

{
  "type": "event",
  "event": "identification-attempt.approved"
}

The trader identification attempt was approved.

identification-attempt.rejected

Example identification-attempt.rejected event

{
  "type": "event",
  "event": "identification-attempt.rejected",
  "context": {
    "rejectReason": "ERROR_NOT_READABLE_ID.MISSING_SIGNATURE"
  }
}

The trader identification attempt was rejected. The event object also contains the following fields:

Field Description
rejectReason Identification attempt rejection reason

Trade events

This section lists the trade-related events that can be received through our WebSocket service

trade.transfer-in-received

Example trade.transfer-in-received event

{
  "type": "event",
  "event": "trade.transfer-in-received",
  "context": {
    "tradeId": "1234"
  }
}

Transfer in was received (but not confirmed yet). Can be used to tell if the bitcoin transaction has been seen by us (Coinify)

Field Description
tradeId Reference to the trade

Trade widget

Trade Widget is a single page app that implements a full trade solution to be embedded in an iframe.

By default, it implements a full customer onboarding flow, but it can also be combined with our API to create a custom onboarding flow. This can be useful e.g. in mobile wallets where the user is already onboarded in the mobile app.

Trade widget

Example to embed widget in iframe for sandbox

<iframe src="https://trade-ui.sandbox.coinify.com?partnerId={replace-with-assigned-id}&primaryColor=blue&cryptoCurrencies=BTC,ETH,XLM" width="100%" height="576px" allow="camera;fullscreen;accelerometer;gyroscope;magnetometer" allowfullscreen></iframe>

Example to embed widget in iframe for production

<iframe src="https://trade-ui.coinify.com?partnerId={replace-with-assigned-id}&primaryColor=blue&cryptoCurrencies=BTC,ETH,XLM" width="100%" height="576px" allow="camera;fullscreen;accelerometer;gyroscope;magnetometer" allowfullscreen></iframe>

Example to pass partnerContext as stringified json

<iframe src="https://trade-ui.coinify.com?partnerId={replace-with-assigned-id}&partnerContext=%7B%22clientId%22%3A123%2C%22refId%22%3A12%7D" width="100%" height="576px" allow="camera;fullscreen;accelerometer;gyroscope;magnetometer" allowfullscreen></iframe>
⚠️ To embed the widget into your web page you must add an iframe element as shown in the examples to the right.

For user identity verification the following attributes are required to be specified for the iframe element:

⚠️ The allow="camera;fullscreen;accelerometer;gyroscope;magnetometer" allowfullscreen attributes must be included to enable the camera and provide the best user experience for user identity verification.
⚠️ In case you are nesting the iframe in another iframe the allow="camera;fullscreen;accelerometer;gyroscope;magnetometer" allowfullscreen attributes must be added to every iframe.

The widget supports a number of configuration options defined using query parameters.

⚠️ The partnerId query parameter must be set to the proper value so that users can be registered with your company

Customised Trade Widget landing page

There is also an option to create a custom landing web page with a dedicated URL for your Trade Widget. The customised Trade Widget landing page allows you to specify the following parameters in order to modify the landing page appearance according to your requirements:

Check out the picture bellow to get a better understanding of which part of the customised landing page do the above mentioned parameters affect.

If you want to set up your customised trade widget landing page, make a request in your dedicated communication channel with Coinify and provide all of the necessary parameters. You can also find example values for the mentioned parameters that you need to provide to Coinify below.

Customised Landing Page

Supported query parameters

⚠️   Every parameter has to be encoded to be used in the URL. E.g. using encodeURIComponent(yourParameter) in JS.
Parameter Description
partnerId id you got from Coinify
partnerName name to be displayed in the widget as partner name, currently used when displaying partner fee if enabled, defaults to ‘Partner’. Can be string or object (just has be be provided as url encoded string, see example)
partnerContext Free context for partners to receive in webhooks, can be stringified JSON as well. Partner context may also be provided using settings.partner-context-changed
primaryColor color of the primary button; Accepted: named value e.g. blue, hash value e.g. #00ff00, or rgba(0,255,0,1)
cryptoCurrencies comma separated list of crypto currencies to support in the widget. Will include only the listed crypto currencies from those supported by default
fiatCurrencies comma separated list of fiat currencies to support in the widget. Will include ony the listed fiat currencies from those supported by default
defaultCryptoCurrency defines the crypto currency initially selected on Buy/Sell quotes if multiple crypto currencies are available
defaultFiatCurrency defines the fiat currency initially selected on Buy/Sell quotes if multiple fiat currencies are available
buyAmount defines initial amount on Buy quotes. Note, often used with targetPage, defaultCryptoCurrency, and defaultFiatCurrency defined
sellAmount defines initial amount on Sell quotes. Note, often used with targetPage, defaultCryptoCurrency, and defaultFiatCurrency defined
isBuyAmountFixed If set to true, the specified buyAmount is locked and cannot be changed from the Trade Widget UI. Accepts true or false values.
isSellAmountFixed If set to true, the specified sellAmount is locked and cannot be changed from the Trade Widget UI. Accepts true or false values.
address defines default wallet address to receive funds at when buying crypto currencies, and default wallet address to refund funds to when selling crypto currencies in case the sell cannot be completed. See details on how to use address below.
addressSignature provides HMAC-SHA-256 signature for address. Only used if a shared secret has been established with Coinify. See details on how to use address below.
confirmMessages defines if external confirmation of trade messages is required. When provided the widget will await confirmation messages from hosting window before continuing flow. See details on how to use messages below.
memo defines an optional memo/destination tag accepted by some crypto currencies. See details on how to use address below.
noSignup defines whether to support signup through the widget. Note the negation! Available values are 'false’ (allow signup for corporate & individuals), 'true’ (allow no signup), 'corporate’ (allow signup only for individual traders) and 'individual’ (allow signup only for corporate traders).
targetPage defines a target landing page other than the default. Available values are 'login’, 'signup’, 'signup-corporate’, 'buy’, 'sell’ and 'trade-history’
refreshToken used for custom onboarding flows. The refeshTokencan be obtained by calling one of the auth endpoints. The refreshToken should be URL encoded so that the browsers interprets it correctly.
transferInMedia list of transfer media for inbound payment method. Available: card, bank, blockchain. See details on how to use below.
transferOutMedia list of transfer media for outbound payment method. Available: bank, blockchain. See details on how to use below.
returnURL the URL that you want to redirect the customer to upon successfuly completed trade. The provided URL must be url-encoded. Note that this query parameter is only available for the Customised Landing Page URL.

NOTE providing these parameters can only remove options from the users view. There may be reasons (e.g legal or contractual) that some options are not available to your users in the underlying API. Adding parameters here will not change that.

In general the widget will adjust to only present what makes sense with the given parameters (ie remove the option to buy if no relevant transfer media are found). It is currently possible to specify parameters that result in an invalid configuration. If you experience a blank widget, try to remove some parameters.

How to use address

<!-- Examples to provide default wallet address for multiple currencies and for single currency -->   

<iframe src="...?address=BTC:2N7gzbQsDnfAFdwabcFqyw1Q8y1ZUpeqUgD,ETH:0x44eae1E05F5f294f0f2a054D16605993FCd627a9&..." />

<iframe src="...?cryptoCurrencies=BTC&address=2N7gzbQsDnfAFdwabcFqyw1Q8y1ZUpeqUgD&..." />

The address parameter should be provided in the format currency1:address1,currency2:address2,... to specify default wallet address per cryptocurrency. Alternatively, if the widget is configured to have only a single currency, you may provide only the address and omit the currency. See examples.

When buying a cryptocurrency the address provided for the currency is used to pre-fill the wallet address input field. Equally, when selling, the address is used to pre-fill the refund wallet address input field, (note, some cryptocurrencies does not require a refund address to be provided). Buying or selling currencies with no default address specified requires the user to manually enter the wallet address.

Address signature

// Javascript example of how to generate signature from secret and address
const crypto = require('crypto');

const address = 'crypto-address';
const secret = 'shared-secret';

const accountSignature = crypto.createHmac('SHA256', secret).update(address).digest('hex');

In order to ensure that the address passed into the widget has not been tampered with, we have the possibility for you to sign the address(es) using a shared secret, and passing the signature to the widget as well.

Address signature follows the same format as the address parameter described above:

Memo

Some crypto currencies accept a memo/destination tag in addition to the wallet address. The value for this can be specified in the memo query parameter.

Memo follows the same format as the address and addressSignature parameters, see examples above.

How to use transferInMedia and transferOutMedia

These parameters have default values and omitting them from the query is equivalent to enabling all available transfer media.

By default the user is able to: 1. Buy crypto using all available payment methods 2. Sell crypto to all available payout methods

By limiting the transfer media available, the payment methods available will also change. This can be used to limit what is possible for the end user in terms of buying & selling as illustrated by the examples below.

Examples showing how to limit payment methods

Use case Parameters Comment
Disable sell, allow buy via card or bank transferInMedia=card,bank Since blockchain is not available as transfer in media, it’s not possible to sell crypto
Disable sell, allow buy bank only transferInMedia=bank Further restrict the allowed transfer in options
Disable sell, allow all buy options transferOutMedia=blockchain If new transfer in media become available, they will be enabled
Disable buy, allow all sell options transferInMedia=blockchain If new transfer out media become available, they will be enabled

Custom onboarding flow

By default, the widget includes a full customer onboarding flow. Depending on the use-case, this may be all that is needed.

But if a different user experience is needed, the widget plays well with the API and in general adopts to use information provided via the API. The API endpoints below might be useful

Widget Sizes

Trade Widget is designed with responsiveness in mind. It will adjust its layout to the size of a screen it is displayed at.

Width

Currently supported layouts include:

Device type min Width max Width
Mobile 320px 575px
Tablet 576px 768px
Computer 769px none

Height

The minimal recommended height of the widget is 576px. Increasing the height will result in stretching the widget, as well as hiding scrollbars on text heavy pages like e.g. Transaction Details.

Widget messages

To send and receive events add a script into your web page as in the example:

<script >
  // receive events
  window.addEventListener("message", receiveMessage, false);
  function receiveMessage(event) {
    if (event.origin === 'https://trade-ui.coinify.com' ||
        event.origin === 'https://trade-ui.sandbox.coinify.com'
    ) {
      const widgetMessage = event.data;

      // your message handler code goes here ...
      // e.g.
      if (widgetMessage.event === 'trade.trade-prepared') {
        // here you could validate the used crypto address 
      } 
  }

  // send events
  const iframe = document.getElementById('your-widget-iframe-id');
  iframe.contentWindow.postMessage({
    type: 'event',
    event: 'trade.confirm-trade-prepared',
    context: {
      confirmed: true
    }
  }, '*');
</script>

The window.postMessage() method is used to send cross-origin messages from the widget to the hosting window. Similarly, window.addEventListener() is used to receive cross-origin messages from the hosting window.

Address confirmation

A common use case for messages is to allow the hosting window to validate the used cryptocurrency address used for a trade. For this use case, the widget is loaded using the confirmMessages=true in the query parameters. The hosting window listens for trade.trade-prepared and trade.trade-created, and once received it validates if the cryptocurrency address used in the trade is as expected, and then responds by posting trade.confirm-trade-prepared and trade.confirm-trade-created respectively with context telling widget whether trade is accepted or declined.

Message format

All messages objects have a type property. Currently, only a single type event is available:

Message types

type Description
event An event related to the widget occurred. The message will have an event property and optionally a context property. See widget events section below.

Message events

Example messages

{
  "type": "event",
  "event": "trade.trade-prepared",
  "context": {
    "baseAmount": 50,
    "baseCurrency": "EUR",
    "quoteAmount": 0.00187817,
    "quoteCurrency": "BTC",
    "transferIn": {
      "medium": "card"
    },
    "transferOut": {
      "details": {
        "account": "ms357UPjpLkQjKifoRKjfsk5Zxedd14uZm"
      },
      "medium": "blockchain"
    }
  }
}
{
  "type": "event",
  "event": "trade.trade-created",
  "context": {
    "createTime": "2021-01-21T15:12:20.300Z",
    "id": 151551,
    "inAmount": 50,
    "inCurrency": "EUR",
    "isPriceQuoteApproximate": false,
    "outAmountExpected": 0.00187817,
    "outCurrency": "BTC",
    "quoteExpireTime": "2021-01-21T15:26:43.396Z",
    "state": "awaiting_transfer_in",
    "traderEmail": "john@doe.com",
    "traderId": 1234,
    "transferIn": {
      "id": 461378, 
      "sendAmount": 53.25, 
      "receiveAmount": 50, 
      "currency": "EUR", 
      "medium": "card"
     },
     "transferOut": {
      "id": 461379, 
      "sendAmount": 0.00187817, 
      "receiveAmount": 0.00160017, 
      "currency": "BTC", 
      "medium": "blockchain",
      "details": {
        "account": "ms357UPjpLkQjKifoRKjfsk5Zxedd14uZm"
      }
    },
    "updateTime": "2021-01-21T15:12:20.300Z"
  }
}
{
  "type": "event",
  "event": "trade.trade-placed",
  "context": {
    "tradeId": 123456
  }
}
{
  "type": "event",
  "event": "trade.confirm-trade-prepared",
  "context": {
    "confirmed": true
  }
}
{
  "type": "event",
  "event": "trade.confirm-trade-created",
  "context": {
    "confirmed": true,
    "tradeId": ,
    "transferInitiated": true
  }
}
{
  "type": "event",
  "event": "settings.partner-context-changed",
  "context": {
    "partnerContext": {
      "yourPartnerContext": "here"
    }
  }
}

The following message events may be sent from the widget

The following message events may be sent to the widget

trade.trade-prepared

A trade has been prepared for review. Sent when the trader has reviewed the transaction, but before actually creating a trade in the backend. The message context contains a partial trade object excluding some properties which are not available until the trade is created in the backend.

trade.trade-created

A trade has been created in the backend. Sent when the trader has reviewed the transaction (and for bank trades has completed KYC) and continues to complete payment. The message context contains a trade object.

trade.trade-placed

The trader has placed a trade. Sent when a user has completed placing a trade (buy or sell) and clicks to return to the quote page. The message context object has the following properties:

Property Description
tradeId id of the trade placed

trade.confirm-trade-prepared

The trader has accepted or rejected the prepared trade. When using the parameter
confirmMessages this event must be sent after a user has either accepted or rejected the prepared trade. The message context object has the following properties:

Property Description
confirmed the confirmation status, true if accepted or false if rejected

trade.confirm-trade-created

The trader has accepted or rejected the created trade. When using the parameter
confirmMessages this event must be sent after a user has either accepted or rejected the created trade. The message context object has the following properties:

Property Description
confirmed the confirmation status, true if accepted or false if rejected
tradeId id of the trade placed
transferInitiated sell trades only, optional, true if the hosting page itself has initiated a transfer, and widget should skip the transfer crypto page

settings.partner-context-changed

The partner context has changed. Sent by the hosting window in case partner context has changed. The provided value will overwrite any current value. Note, a default value may be set using the partnerContext query parameter. The message context object has the following properties:

Property Description
partnerContext Free context object

Trade Widget Sandbox Testing

In order to test trades in the Trade Widget, use the widget’s sandbox environment base URL. Transactions completed in the sandbox environment have no value. Complete flow for Buy trades is available only for USDC on Ethereum's Sepolia testnet and BTCt on BTC testnet3 network. However, the complete Sell trades flow in sandbox is available only using the BTC testnet3 network.

Currency Network Wallet Setup Tutorial
USDC Sepolia testnet Metamask Sepolia and USDC Setup. Recommended for Buy Trade testing.
BTCt Testnet3 Bitcoin Testnet Wallet Setup. Recommended for Sell Trade testing.

Use test credit card details to complete card buy trades

Use fake bank transfer API endpoint to complete bank buy trades

Buy Button

Buy Button is a simple web component that opens up trade widget in a 768x576 popup.

How to use

Add the following code into your web page: for sandbox

  <script src="https://trade-ui.sandbox.coinify.com/components/buy-button.js"></script>
  ...
  <buy-button
        url="https://trade-ui.sandbox.coinify.com?partnerId={replace-with-assigned-id}&primaryColor=blue&cryptoCurrencies=BTC,ETH,XLM"
      />

for production

  <script src="https://trade-ui.coinify.com/components/buy-button.js"></script>
  ...
  <buy-button
        url="https://trade-ui.coinify.com?partnerId={replace-with-assigned-id}&primaryColor=blue&cryptoCurrencies=BTC,ETH,XLM"
      />

url property points to the trade widget just as in the example.

⚠️ The partnerId queryString parameter must be set to the proper value so that users can be registered with your company

Styling the Buy Button

Custom CSS properties exposed

Property Description
background-color color of the button
color color of the text

Style using custom CSS properties

  <style type="text/css">
    buy-button {
      --background-color: #8fc065;
    }
  </style>

Style using CSS

  buy-button button {
    background-color: #8fc065;
  }