Verify API Reference
Dive into our full API Reference Documentation and seamlessly integrate 2FA functionalities into your website or application.
Our APIs use HTTP verbs and a RESTful endpoint structure. An access key and secret are used as the API Authorization framework. Request and response payloads are formatted as JSON, using UTF-8 encoding and URL encoded values.
API Endpoint
In order to use Verify APIs, you need to first sign up for free at app.2fa.link
https://api.2fa.link/verify
Credentials
All requests to 2FA.link’s REST API need to be authenticated using HTTP Basic Authentication.
API key/secret pair are credentials you can create and revoke in the admin dashboard. These are typically easier to work with for your projects. You can quickly delete them to revoke access if they are compromised or create mutiple key/secret pairs for different use cases. You can also suspend an API key if neccesary to temporary block access.
API Key | API Secret |
---|---|
Username | Password |
One of the most common mistakes that is made with API keys is to inadvertently check them into public repositories on platforms such as GitHub. From these public repositories, fraudsters can search and steal your API access key and use it to send Spam messages and also drain your account balance. The main takeaway is don’t hard-code your API access key and don’t check it into a public code repository.
HTTP Method
2FA.link only support the POST HTTP methods on various resources.
You can create or query a resource using the HTTP POST method on a resource URI. All you need is to provide the relevant fields in JSON! Here’s a POST request that will create a Verify Request using the 2FA.link API.
Note: All trial accounts are rate-limited at 1 request / second / application (api)
Verify Request
Use this to create a new Verify request, we would attempt to sent it by Push Notification to the user. If user is not registered with our 2FA mobile app, it would fallback to OTP SMS.

curl -X POST "https://API_KEY:API_SECRET@https://api.2fa.link/verify"
-H "accept: application/json"
-H "Content-Type: application/json; charset=utf-8"
-d '{
"from":"2FA",
"to":"6598765432",
"mode":"verify"
}'
$fields = [
'from' => '2FA',
'to' => '6598765432',
'mode' => 'verify'
];
$headers = [
'Accept: application/json',
'Content-Type: application/json; charset=utf-8'
];
$url = "https://API_KEY:API_SECRET@api.2fa.link/verify";
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($fields));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
curl_close($curl);
Request-body (content-type: application/json)
Key | Mandatory | Accepted | Description |
---|---|---|---|
mode | Yes | “verify” | Mode of operation |
from | No |
[A-Za-z0-9 ]{2,11}
|
Sender Name to Display (Default: “2FA.link”) |
to | Yes |
[0-9]{10,15}
|
Receiver in E.164 Format |
length | No | 6-10 | OTP length (applicable for SMS only) |
no-otl | No | 0 or 1 | One Time Link for SMS (Default: 0) |
no-otp | No | 0 or 1 | One Time Pin for SMS (Default: 0) |
channel | No | “sms” | Channel to sent (Default: “push”) |
purpose | No | [A-Za-z0-9 .,$]{10,50} | Purpose of this 2FA request to be shown to user |

Response-body (Positive)
{
"errorcode":100,
"errormsg":"Success",
"channel":"SMS",
"request_id":"600EC8DB-FFMRSQGCNG"
}
Response-body (Negative)
{
"errorcode":207,
"errormsg":"Incorrect MODE Field Format"
}
Note: If the channel is “SMS”, display an OTP input page to capture user’s input.
Verify “Check” Request
Use this API call to verify the OTP (SMS) received. This only apply for Verify request that has not reached Final status (Approve/Decline/Expired).

curl -X POST "https://API_KEY:API_SECRET@https://api.2fa.link/verify"
-H "accept: application/json"
-H "Content-Type: application/json; charset=utf-8"
-d '{
"request_id":"600EC8DB-FFMRSQGCNG",
"otp":"123456",
"mode":"check"
}'
$fields = [
'request_id' => '600EC8DB-FFMRSQGCNG',
'otp' => '123456',
'mode' => 'check'
];
$headers = [
'Accept: application/json',
'Content-Type: application/json; charset=utf-8'
];
$url = "https://API_KEY:API_SECRET@api.2fa.link/verify";
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($fields));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
curl_close($curl);
Request-body (content-type: application/json)
Key | Mandatory | Accepted | Description |
---|---|---|---|
mode | Yes | “check” | Mode of operation |
request_id | Yes |
–
|
ID of Verify request |
otp | Yes |
[0-9]{6,10}
|
OTP received via SMS or 2FA mobile app |
Response-body (Positive)
{
"request_id":"600ED8FE-YEQJDVLLQI",
"from":"2FA",
"to":"6598765432",
"status":"Success",
"statuscode":"600",
"timestamp_submitted":"2021-01-25T14:43:10+00:00",
"timestamp_finalized":"2021-01-25T14:43:50+00:00"
}
Response-body (Negative)
{
"request_id":"600EC8DB-FFMRSQGCNG",
"status":"Expired",
"statuscode":"602"
}
Response-body
Key | Description |
---|---|
request_id | ID for Verify request |
from | Sender Name |
to | Receiver |
status | Status of OTP checking |
timestamp_submitted | Timestamp of Verify request |
timestamp_finalized | Timestamp of Final status |
Status
statuscode | status | Description |
---|---|---|
600 | Success | One-Time-Pin matched |
601 | Failed | One-Time-Pin do not matched |
602 | Expired | One-Time-Pin expired (3 attempts reached) |
603 | Not Found | Verify request not found (Over 3 minutes or already reached final status) |
Verify “Query” Request
Use this API call to query a “Verify” request.
curl -X POST "https://API_KEY:API_SECRET@https://api.2fa.link/verify"
-H "accept: application/json"
-H "Content-Type: application/json; charset=utf-8"
-d '{
"request_id":"600EC8DB-FFMRSQGCNG",
"mode":"query"
}'
$fields = [
'request_id' => '600EC8DB-FFMRSQGCNG',
'mode' => 'query'
];
$headers = [
'Accept: application/json',
'Content-Type: application/json; charset=utf-8'
];
$url = "https://API_KEY:API_SECRET@api.2fa.link/verify";
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($fields));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
curl_close($curl);
Request-body (content-type: application/json)
Key | Mandatory | Accepted | Description |
---|---|---|---|
mode | Yes | “query” | Mode of operation |
request_id | Yes |
–
|
ID of Verify request |
Response-body (Positive)
{
"request_id":"600EC8DB-FFMRSQGCNG",
"from":"2FA",
"to":"6598765432",
"channel":"SMS",
"app_name":"2FA Portal",
"status":"Approved",
"statuscode":"700",
"msg count":"1",
"price":"-0.05",
"timestamp_submitted":"2021-01-25T13:34:19+00:00",
"timestamp_finalized":"2021-01-25T13:38:01+00:00"
}
Response-body (Negative)
{
"request_id":"600EC8DB-FFMRSQGC",
"status":"Not Found",
"statuscode":"704"
}
Response-body
Key | Description |
---|---|
request_id | ID for Verify request |
from | Sender Name |
to | Receiver |
channel | Channel used to send Verify request |
app_name | Application Name |
status | Status of Verify request |
statuscode | Status Code |
msg_count | Messages sent |
price | Price charged (EURO) |
timestamp_submitted | Timestamp of Verify request |
timestamp_finalized | Timestamp of Final status |
Status
statuscode | status | Description |
---|---|---|
700 | Approved | User Approved this request |
701 | Declined | User Declined this request |
702 | Expired (Max duration reached) | Verify request expired (3 minutes) |
703 | Expired (Max tries reached) | OTP “check” attempted reached (3 times) |
704 | Not Found | Verify request not found |
705 | Verify Error | Error encountered with Verify request |
706 | Verify Push Sent | Verify sent via Push channel (Pending approval) |
707 | Verify SMS Sent | Verify sent via SMS channel (Pending approval) |
708 | Verify SMS Delivered | Verify OTP SMS delivered (Pending approval) |
709 | Verify SMS Undelivered | Verify OTP SMS undelivered (Pending approval) |
Verify “TOTP” Request
Use this API call to make a “Verify TOTP” request.
Note: All T-OTP request are rate-limited at 1 request / second / mobile number

curl -X POST "https://API_KEY:API_SECRET@https://api.2fa.link/verify"
-H "accept: application/json"
-H "Content-Type: application/json; charset=utf-8"
-d '{
"mode":"verify",
"channel":"totp",
"to":"6598765432",
"totp":"123456"
}'
$fields = [
'mode' => 'verify',
'channel' => 'totp',
'to' => '6598765432',
'totp' => '123456'
];
$headers = [
'Accept: application/json',
'Content-Type: application/json; charset=utf-8'
];
$url = "https://API_KEY:API_SECRET@api.2fa.link/verify";
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($fields));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
curl_close($curl);
Request-body (content-type: application/json)
Key | Mandatory | Accepted | Description |
---|---|---|---|
mode | Yes | “verify” | Mode of operation |
channel | Yes | “totp” | Channel as TOTP |
to | Yes | [0-9]{10,15} | Receiver in E.164 Format |
totp | Yes | [0-9]{6} | TOTP received from user via 2FA mobile app |
Response-body (Positive)
{
"request_id":"602B5ED0-DGGJTSFRBY",
"to":"6598765432",
"status":"Success",
"statuscode":800,
"timestamp_submitted":"2021-02-16T05:57:36+00:00"
}
Response-body (Negative)
{
"request_id":"602B5E20-DGMVGJJPUU",
"to":"6598765432",
"status":"Fail",
"statuscode":801,
"timestamp_submitted":"2021-02-16T05:54:40+00:00"
}
Response-body
Key | Description |
---|---|
request_id | ID for Verify request |
to | Receiver |
status | Status of Verify TOTP request |
statuscode | Status Code |
timestamp_submitted | Timestamp of Verify TOTP request |
Status
statuscode | status | Description |
---|---|---|
800 | Success | T-OTP matched |
801 | Fail | T-OTP does not match |
Callback
A callback would be POST to your provided webhook when the users provide their response via the 2FA mobile app or One-Time-Link (OTL) web-app. This mark the final status of the Verify request. For security, you can whitelist traffic from only our IP address (168.119.166.186). Do note that more sites may be added in the future and you would be informed accordingly.

{
"request_id":"600E2A82-IKEIOSXRZU",
"from":"2FA",
"to":"6598765432",
"status":"Approved",
"statuscode":"700",
"timestamp_submitted":"2021-01-25T02:18:42+00:00",
"timestamp_finalized":"2021-01-25T02:19:04+00:00",
"ipaddress":"121.6.57.0"
}
Request-body (content-type: application/json)
Key | Description |
---|---|
request_id | ID of Verify Request |
from | Sender Name |
to | Receiver |
status | Final Status |
timestamp_submitted | Timestamp of Verify request |
timestamp_finalized | Timestamp of Final status |
ipaddress | IP address of client, last octet is masked for user privacy |
Status
statuscode | status | Description |
---|---|---|
700 | Approved | User Approved this request |
701 | Declined | User Declined this request |
Error Codes
errorcode | errormsg |
---|---|
100 | Success |
101 | Invalid API Key or Secret |
102 | Invalid Account |
103 | Rate Limit Reached |
104 | Insufficient Credit in Account |
201 | Missing Mandatory Field(s) for Requested Mode |
202 | Invalid API Key or Secret Format |
203 | Incorrect TO Field Format |
204 | Incorrect FROM Field Format |
205 | Incorrect LENGTH Field Format |
206 | Incorrect CHANNEL Field Format |
207 | Incorrect MODE Field Format |
208 | Incorrect OTP Field Format |
209 | Both no-otp and no-otl Fields cannot be 1 |
210 | Incorrect TOTP Field Format |
211 | Incorrect REQUEST_ID Field Format |
301 | SMS Rejected |
401 | Temporary Error, Please try again later! |