Silent Authentication API
Version: v1.0.0
Specification Release Notes Other versions
Silent Authentication API provides OAuth2-based authentication service that verifies whether an authentication request originates from the same subscriber by resolving network session information.
Typical flow:
- Client initiates OAuth2 authorization with
/oauth2/authorizeusinglogin_hintcontaining the MSISDN. - The service extracts MSISDN, client IP/port and timestamp, then resolves network session data.
- The service checks if the provided MSISDN matches the one associated with the resolved network session.
- Client exchanges the code for access token via
/oauth2/token. - The access token contains a
mobile_idclaim when verification succeeds. - Optionally, client can call
/oauth2/userinfowith the access token to retrieve verification results without decoding the JWT.
Base URLs
OAuth2
OAuth2 endpoints for silent authentication flow.
OAuth2 authorization
Code samples
# You can also use wget
curl -X GET 'https://api.tyntec.com/silent-auth/v1/oauth2/authorize?response_type=code& \
client_id=client& \
state=xyz12345& \
scope=openid%20tt%3Aphone_verify& \
redirect_uri=https%3A%2F%2Fclient.example.com%2Fcallback'
GET https://api.tyntec.com/silent-auth/v1/oauth2/authorize?response_type=code&client_id=client&state=xyz12345&scope=openid%20tt%3Aphone_verify&redirect_uri=https%3A%2F%2Fclient.example.com%2Fcallback HTTP/1.1
Host: api.tyntec.com
fetch('https://api.tyntec.com/silent-auth/v1/oauth2/authorize?response_type=code&client_id=client&state=xyz12345&scope=openid%20tt%3Aphone_verify&redirect_uri=https%3A%2F%2Fclient.example.com%2Fcallback',
{
method: 'GET'
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
require 'rest-client'
require 'json'
result = RestClient.get 'https://api.tyntec.com/silent-auth/v1/oauth2/authorize',
params: {
'response_type' => 'string',
'client_id' => 'string',
'state' => 'string',
'scope' => 'string',
'redirect_uri' => 'string(uri)'
}
p JSON.parse(result)
import requests
r = requests.get('https://api.tyntec.com/silent-auth/v1/oauth2/authorize', params={
'response_type': 'code', 'client_id': 'client', 'state': 'xyz12345', 'scope': 'openid tt:phone_verify', 'redirect_uri': 'https://client.example.com/callback'
)
print(r.json())
<?php
require 'vendor/autoload.php';
$client = new \GuzzleHttp\Client();
// Define array of request body.
$request_body = array();
try {
$response = $client->request('GET','https://api.tyntec.com/silent-auth/v1/oauth2/authorize', array(
'headers' => $headers,
'json' => $request_body,
)
);
print_r($response->getBody()->getContents());
}
catch (\GuzzleHttp\Exception\BadResponseException $e) {
// handle exception or api errors.
print_r($e->getMessage());
}
// ...
URL obj = new URL("https://api.tyntec.com/silent-auth/v1/oauth2/authorize?response_type=code&client_id=client&state=xyz12345&scope=openid%20tt%3Aphone_verify&redirect_uri=https%3A%2F%2Fclient.example.com%2Fcallback");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
package main
import (
"bytes"
"net/http"
)
func main() {
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("GET", "https://api.tyntec.com/silent-auth/v1/oauth2/authorize", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
GET /oauth2/authorize
Initiates the OAuth2 authorization flow with silent authentication. The client provides
the subscriber's MSISDN in the login_hint parameter. The service resolves the network session
and verifies the MSISDN, then redirects to the callback URI with an authorization code.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| response_type | query | string | true | OAuth2 response type (must be 'code') |
| client_id | query | string | true | Client identifier, provided during the onboarding process |
| state | query | string | true | Opaque value used by the client to maintain state between request and callback |
| scope | query | string | true | Space-delimited scope values. |
| redirect_uri | query | string(uri) | true | Client callback URI where the authorization code will be sent |
| login_hint | query | string | false | Phone number in the international format with or without the leading “+” or “00” prefix |
Detailed description
scope: Space-delimited scope values.
Possible values:
- tt:phone_verify
- tt:mobile_id
login_hint: Phone number in the international format with or without the leading “+” or “00” prefix
Examples: +491234567890, 00491234567890 or 491234567890.
Client should provide the login_hint for higher security and better user experience, but it is not mandatory.
If the login_hint is not provided, a pop-up window will be shown to the user to input their MSISDN.
Enumerated values
| Parameter | Value |
|---|---|
| response_type | code |
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 302 | Found | Redirects the user-agent to the client-provided callback URI with OAuth 2.0 parameters. |
Success case:
Returns authorization code and state in query parameters.
Example: https://client.example.com/callback?code=9oubNbRC9el6-KoSda3NvBQlf3-Bh...&state=xyz12345
Error cases:
Returns error, error_description, and state in query parameters. Query parameter values are URL-encoded (spaces may appear as "+" in application/x-www-form-urlencoded encoding).
Error parameter names follow OAuth 2.0 specification:
error: Error code (e.g., invalid_request, invalid_scope, no_data_session)error_description: Human-readable error explanationstate: Opaque value from the authorization request
Example: https://client.example.com/callback?error=invalid_request&error_description=OAuth+2.0+Parameter:+client_id&state=xyz12345|None|
Response Headers
| Status | Header | Type | Format | Description |
|---|---|---|---|---|
| 302 | Location | string | uri | Redirect URI with query parameters. For success: code and state. For errors: error, error_description, and state. |
OAuth2 token
Code samples
curl -X POST "https://api.tyntec.com/silent-auth/v1/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "apiKey: <apiKey>" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=9oubNbRC9el6-KoSda3NvBQlf3-Bh..." \
--data-urlencode "redirect_uri=https://client.example.com/callback" \
--data-urlencode "client_id=client" \
--data-urlencode "client_secret=12345"
import requests
url = "https://api.tyntec.com/silent-auth/v1/oauth2/token"
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"apiKey": "<apiKey>"
}
data = {
"grant_type": "authorization_code",
"code": "9oubNbRC9el6-KoSda3NvBQlf3-Bh...",
"redirect_uri": "https://client.example.com/callback",
"client_id": "client",
"client_secret": "12345"
}
response = requests.post(url, headers=headers, data=data)
print(response.json())
const data = new URLSearchParams({
grant_type: 'authorization_code',
code: '9oubNbRC9el6-KoSda3NvBQlf3-Bh...',
redirect_uri: 'https://client.example.com/callback',
client_id: 'client',
client_secret: '12345'
});
fetch('https://api.tyntec.com/silent-auth/v1/oauth2/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'apiKey': '<apiKey>'
},
body: data
})
.then(response => response.json())
.then(data => console.log(data));
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
URL url = new URL("https://api.tyntec.com/silent-auth/v1/oauth2/token");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("apiKey", "<apiKey>");
conn.setDoOutput(true);
String data = "grant_type=authorization_code" +
"&code=9oubNbRC9el6-KoSda3NvBQlf3-Bh..." +
"&redirect_uri=https://client.example.com/callback" +
"&client_id=client" +
"&client_secret=12345";
try (OutputStream os = conn.getOutputStream()) {
byte[] input = data.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
try (BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
POST /silent-auth/v1/oauth2/token HTTP/1.1
Host: api.tyntec.com
Content-Type: application/x-www-form-urlencoded
apiKey: <apiKey>
grant_type=authorization_code&code=9oubNbRC9el6-KoSda3NvBQlf3-Bh...&redirect_uri=https://client.example.com/callback&client_id=client&client_secret=12345
require 'net/http'
require 'uri'
require 'json'
uri = URI.parse("https://api.tyntec.com/silent-auth/v1/oauth2/token")
request = Net::HTTP::Post.new(uri)
request.content_type = "application/x-www-form-urlencoded"
request["apiKey"] = "<apiKey>"
request.set_form_data(
"grant_type" => "authorization_code",
"code" => "9oubNbRC9el6-KoSda3NvBQlf3-Bh...",
"redirect_uri" => "https://client.example.com/callback",
"client_id" => "client",
"client_secret" => "12345"
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts JSON.parse(response.body)
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
func main() {
apiUrl := "https://api.tyntec.com/silent-auth/v1/oauth2/token"
data := url.Values{}
data.Set("grant_type", "authorization_code")
data.Set("code", "9oubNbRC9el6-KoSda3NvBQlf3-Bh...")
data.Set("redirect_uri", "https://client.example.com/callback")
data.Set("client_id", "client")
data.Set("client_secret", "12345")
req, err := http.NewRequest("POST", apiUrl, strings.NewReader(data.Encode()))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("apiKey", "<apiKey>")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(body))
}
<?php
$url = "https://api.tyntec.com/silent-auth/v1/oauth2/token";
$data = array(
'grant_type' => 'authorization_code',
'code' => '9oubNbRC9el6-KoSda3NvBQlf3-Bh...',
'redirect_uri' => 'https://client.example.com/callback',
'client_id' => 'client',
'client_secret' => '12345'
);
$options = array(
'http' => array(
'header' => "Content-Type: application/x-www-form-urlencoded\r\n" .
"apiKey: <apiKey>\r\n",
'method' => 'POST',
'content' => http_build_query($data)
)
);
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if ($result === FALSE) {
die('Error');
}
$response = json_decode($result, true);
print_r($response);
?>
POST /oauth2/token
Exchange an authorization code for access token and refresh token. The access token
contains a mobile_id claim when silent authentication verification succeeded.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| body | body | object | true | none |
| » grant_type | body | string | true | OAuth2 grant type (must be 'authorization_code') |
| » code | body | string | true | Authorization code received from /oauth2/authorize |
| » redirect_uri | body | string(uri) | true | Redirect URI that was used in the authorization request |
| » client_id | body | string | true | Client identifier |
| » client_secret | body | string | true | Client secret |
Example responses
Successful token response
{
"access_token": "eyJraWQiOiIyYzc5NDNmMy00YzIyLTQ5...",
"refresh_token": "DawkDPIXqUDqyjHfzcifAsc8gvL1jBys...",
"scope": "openid tt:mobile_id",
"token_type": "Bearer",
"expires_in": 86399
}
400 Response
{
"error": "invalid_request",
"error_description": "Client specified an invalid argument, request body or query param."
}
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | Successful token response | TokenResponse |
| 400 | Bad Request | Bad Request - Invalid request parameters | BadRequestError |
| 401 | Unauthorized | Unauthorized - Invalid client credentials or authorization code | UnauthorizedError |
| 403 | Forbidden | Forbidden | PermissionDeniedError |
| 500 | Internal Server Error | Internal Server Error | InternalServerError |
OAuth2 UserInfo
Code samples
# You can also use wget
curl -X GET 'https://api.tyntec.com/silent-auth/v1/oauth2/userinfo' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer {access-token}'
GET https://api.tyntec.com/silent-auth/v1/oauth2/userinfo HTTP/1.1
Host: api.tyntec.com
Accept: application/json
Authorization: Bearer {access-token}
const headers = {
'Accept':'application/json',
'Authorization':'Bearer {access-token}'
};
fetch('https://api.tyntec.com/silent-auth/v1/oauth2/userinfo',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
require 'rest-client'
require 'json'
headers = {
'Accept' => 'application/json',
'Authorization' => 'Bearer {access-token}'
}
result = RestClient.get 'https://api.tyntec.com/silent-auth/v1/oauth2/userinfo',
params: {
}, headers: headers
p JSON.parse(result)
import requests
headers = {
'Accept': 'application/json',
'Authorization': 'Bearer {access-token}'
}
r = requests.get('https://api.tyntec.com/silent-auth/v1/oauth2/userinfo', params={
}, headers = headers)
print(r.json())
<?php
require 'vendor/autoload.php';
$headers = array(
'Accept' => 'application/json',
'Authorization' => 'Bearer {access-token}',
);
$client = new \GuzzleHttp\Client();
// Define array of request body.
$request_body = array();
try {
$response = $client->request('GET','https://api.tyntec.com/silent-auth/v1/oauth2/userinfo', array(
'headers' => $headers,
'json' => $request_body,
)
);
print_r($response->getBody()->getContents());
}
catch (\GuzzleHttp\Exception\BadResponseException $e) {
// handle exception or api errors.
print_r($e->getMessage());
}
// ...
URL obj = new URL("https://api.tyntec.com/silent-auth/v1/oauth2/userinfo");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Accept", "application/json");
con.setRequestProperty("Authorization", "Bearer {access-token}");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Accept": []string{"application/json"},
"Authorization": []string{"Bearer {access-token}"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("GET", "https://api.tyntec.com/silent-auth/v1/oauth2/userinfo", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
GET /oauth2/userinfo
Retrieve authentication results using the access token from the token endpoint. This standard OpenID Connect UserInfo endpoint returns the mobile_id, phone number verification status, and user identifier.
Clients can call this endpoint with Bearer token authentication instead of manually decoding the access_token JWT.
Example responses
Successful userinfo response
{
"sub": "99dd91d1-c949-433c-bd9e-0682eb6d6d26",
"mobile_id": "2d83f2c1f618313f4b24516913d4e45e00ee289af2bc428d897b175adc5775e643e52bc7286eeca02353e4eb99fccacbe263ec922dbfdde6ed64be90f0e2f780",
"login_hint": "+491123456789",
"phone_number_verified": "true"
}
{
"sub": "anonymous",
"mobile_id": "2d83f2c1f618313f4b24516913d4e45e00ee289af2bc428d897b175adc5775e643e52bc7286eeca02353e4eb99fccacbe263ec922dbfdde6ed64be90f0e2f780",
"login_hint": "+491123456789",
"phone_number_verified": "false"
}
401 Response
{
"error": "invalid_client",
"error_description": "Request not authenticated due to missing, invalid, or expired credentials. A new authentication is required."
}
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | Successful userinfo response | UserInfoResponse |
| 401 | Unauthorized | Unauthorized - Invalid or expired access token | UnauthorizedError |
| 403 | Forbidden | Forbidden | PermissionDeniedError |
| 500 | Internal Server Error | Internal Server Error | InternalServerError |
Schemas
CallbackUri
"https://client.example.com/callback?code=9oubNbRC9el6-KoSda3NvBQlf3-Bh...&state=xyz12345"
Client callback URI where the authorization code will be sent
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| anonymous | string(uri) | false | none | Client callback URI where the authorization code will be sent |
TokenResponse
{
"access_token": "eyJraWQiOiIyYzc5NDNmMy00YzIyLTQ5MzUtOTBmZi1kOWVkNTVmYTYxN2QiLCJhbGciOiJSUzI1NiJ9...",
"refresh_token": "DawkDPIXqUDqyjHfzcifAsc8gvL1jBysWTe4vgXy1PbJGk51Ec6mRIZnCJh7nzRBG1MnE7C...",
"scope": "openid tt:mobile_id tt:phone_verify",
"token_type": "Bearer",
"expires_in": 86399
}
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| access_token | string | false | none | OAuth2 access token (JWT format) containing the mobile_id claim when verification succeeded |
| refresh_token | string | false | none | OAuth2 refresh token |
| scope | string | false | none | Space-delimited scope values granted |
| token_type | string | false | none | Token type (always 'Bearer') |
| expires_in | integer | false | none | Access token lifetime in seconds |
UserInfoResponse
{
"sub": "99dd91d1-c949-433c-bd9e-0682eb6d6d26",
"mobile_id": "2d83f2c1f618313f4b24516913d4e45e00ee289af2bc428d897b175adc5775e643e52bc7286eeca02353e4eb99fccacbe263ec922dbfdde6ed64be90f0e2f780",
"login_hint": "+491123456789",
"phone_number_verified": "true"
}
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| sub | string | true | none | Subject identifier is a UUID of the request. Returns 'anonymous' when verification fails (provided MSISDN does not match the resolved MSISDN). |
| mobile_id | string | true | none | Mobile ID can be used as a network identifier of a user. |
| login_hint | string | true | none | Phone number value that was submitted in the authorization request. |
| phone_number_verified | string | true | none | Boolean value indicating whether the submitted phone number matches the resolved network MSISDN. |
Enumerated values
| Property | Value |
|---|---|
| phone_number_verified | true |
| phone_number_verified | false |
BadRequestError
{
"error": "invalid_request",
"error_description": "Client specified an invalid argument, request body or query param."
}
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| error | string | false | none | Error code |
| error_description | string | false | none | Error message |
UnauthorizedError
{
"error": "invalid_client",
"error_description": "Request not authenticated due to missing, invalid, or expired credentials. A new authentication is required."
}
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| error | string | false | none | Error code |
| error_description | string | false | none | Error message |
PermissionDeniedError
{
"error": "permission_denied",
"error_description": "Client does not have sufficient permissions to perform this action."
}
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| error | string | false | none | Error code |
| error_description | string | false | none | Error message |
NotFoundError
{
"error": "not_found",
"error_description": "The specified resource is not found."
}
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| error | string | false | none | Error code |
| error_description | string | false | none | Error message |
InternalServerError
{
"error": "internal_server_error",
"error_description": "Request could not be processed"
}
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| error | string | false | none | Error code |
| error_description | string | false | none | Error message |