OpenTIN API – AWS Data Exchange Technical Documentation

OpenTIN API via AWS Data Exchange

The AWS Data Exchange interface is the original OpenTIN API integration, available through AWS Data Exchange.

With this interface, you access the API by subscribing to the OpenTIN dataset on AWS Data Exchange. API calls are made using the AWS SDK (boto3 for Python, Amazon.DataExchange for C#) via the send_api_asset method — no HTTP client or bearer token required.


How it works
1. Subscribe
Subscribe to the TIN Validation API dataset on AWS Data Exchange from your AWS account.
2. Resolve IDs
Use the AWS SDK to list your entitled datasets and resolve the latest DataSetId, RevisionId, and AssetId.
3. Call the API
Use send_api_asset with the path /tin_validate/{country}/{type}/{tin} to validate a TIN number.

For the full API specification, path parameters and response codes, see the OpenTIN Technical Documentation.


# OpenTIN API full example code

import sys
import traceback
import json
import boto3
from datetime import datetime
import os

import pytz
tz_FR = pytz.timezone('Europe/Berlin') 
    

def running_on_aws_mwaa():
    if 'AWS_EXECUTION_ENV' in os.environ:
        return True
    else:
        return False
    

if running_on_aws_mwaa():
    mysession = boto3.session.Session(region_name='eu-central-1')
else:
    mysession = boto3.session.Session(profile_name='___AWS_PROFILE_NAME___', region_name='eu-central-1')

try:
    print('-----------------------------------------------------------')
    dex = mysession.client('dataexchange', region_name='eu-central-1')

    response = dex.list_data_sets(
        MaxResults=200,
        Origin='ENTITLED'
    )

    # print(response)
    # print(response['DataSets'])
    most_recent_time = datetime(2022, 1, 1, 0, 0, 0, 0, tzinfo=tz_FR)
    for r in response['DataSets']:
        # print(r)
        if r['UpdatedAt'] > most_recent_time and 'TIN' in r['Name'] and 'API' in r['Name']:
            print("Found matching DataSet:", r['Id'])
            dataset_id = r['Id']
            most_recent_time = r['UpdatedAt']
    print('-----------------------------------------------------------')

    response = dex.list_data_set_revisions(
        DataSetId=dataset_id,
        MaxResults=200
    )
    # print(response)
    # print(response['Revisions'])
    most_recent_time = datetime(2022, 1, 1, 0, 0, 0, 0, tzinfo=tz_FR)
    for r in response['Revisions']:
        # print(r)
        if r['UpdatedAt'] > most_recent_time:
            print("Found matching Revision:", r['Id'])
            revision_id = r['Id']
            most_recent_time = r['UpdatedAt']

    print('-----------------------------------------------------------')

    response = dex.list_revision_assets(
        DataSetId=dataset_id,
        MaxResults=200,
        RevisionId=revision_id
    )
    # print(response)
    # print(response['Assets'])
    most_recent_time = datetime(2022, 1, 1, 0, 0, 0, 0, tzinfo=tz_FR)
    for r in response['Assets']:
        # print(r)
        if r['UpdatedAt'] > most_recent_time:
            print("Found matching Asset:", r['Id'])
            asset_id = r['Id']
            most_recent_time = r['UpdatedAt']
    print('-----------------------------------------------------------')
except Exception as e:
    print("EXCEPTION:", e)
    # Set some defaults, for example an asset_id/dataset_id/revision_id that was tested as working in the past
    asset_id = '__SOME_DEFAULT_FALUE__'
    dataset_id = '__SOME_DEFAULT_FALUE__'
    revision_id ='__SOME_DEFAULT_FALUE__'

try:
    tin_country = 'US'
    user_type_id = 'I'
    tin = '123456789'

    path = f'''/tin_validate/{tin_country}/{user_type_id}/{tin}'''
    print("Calling OpenTIN API using the path:", path)

    response = dex.send_api_asset(
        Body='',
        QueryStringParameters={},
        AssetId=asset_id,
        DataSetId=dataset_id,
        RequestHeaders={},
        Method='GET',
        Path=path,
        RevisionId=revision_id
    )

    print(response['Body'])
    str_answer = response['Body']
    opentin_answer = json.loads(response['Body'])
    if 'tin_valid' in opentin_answer:
        print('tin_valid:', opentin_answer['tin_valid'])
    if 'message' in opentin_answer:
        print('message:', opentin_answer['message'])

except Exception as e:
    print("Exception calling OpenTIN API:", e)

# OpenTIN API full example code END

 

using System;
using System.Threading.Tasks;
using Amazon.DataExchange;
using Amazon.DataExchange.Model;
using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;
using Amazon;

/*
// .csproj file:

  
    Exe
    netcoreapp8
  
  
    
    
  
  

*/
namespace AwsDataExchangeSample
{
  class Program
  {
    static void Main(string[] args)
    {
        var chain = new CredentialProfileStoreChain();
        AWSCredentials awsCredentials;
        if (chain.TryGetAWSCredentials("credentials-profile-name", out awsCredentials))
        {
            // Connect to Data Exchange
            AmazonDataExchangeClient client = new AmazonDataExchangeClient(awsCredentials, RegionEndpoint.EUCentral1);

            // List the APIs we subscribed to
            ListDataSetsRequest listDataSetsRequest = new ListDataSetsRequest();
            listDataSetsRequest.Origin = "ENTITLED";

            Task dataSetsRequestTask = client.ListDataSetsAsync(listDataSetsRequest);
            dataSetsRequestTask.Wait();

            string tin_validation_api_id = System.String.Empty;
            foreach (DataSetEntry dataSetEntry in dataSetsRequestTask.Result.DataSets)
            {
                Console.WriteLine("{0}/{1}: {2}\n  {3}",
                    dataSetEntry.OriginDetails.ProductId,
                    dataSetEntry.Id,
                    dataSetEntry.Name,
                    dataSetEntry.Description);

                if (dataSetEntry.Name == "TIN Validation API")
                    tin_validation_api_id = dataSetEntry.Id;
            }
            Console.WriteLine("--> OK found the TIN Validation API id: {0}\n", tin_validation_api_id);

            // List the API revisions and find the latest one
            ListDataSetRevisionsRequest listDataSetRevisionsRequest = new ListDataSetRevisionsRequest();
            listDataSetRevisionsRequest.DataSetId = tin_validation_api_id;

            Task dataSetRevisionsTask = client.ListDataSetRevisionsAsync(listDataSetRevisionsRequest);
            dataSetRevisionsTask.Wait();

            string tin_validation_api_latest_revision_id = System.String.Empty;
            DateTime tin_validation_api_latest_revision_date = new DateTime(2000,01,01);
            foreach (RevisionEntry dataSetRevision in dataSetRevisionsTask.Result.Revisions)
            {
                Console.WriteLine("{0} - [{1}] / [{2}]",
                    dataSetRevision.Id,
                    dataSetRevision.UpdatedAt,
                    dataSetRevision.Comment);

                if (dataSetRevision.UpdatedAt > tin_validation_api_latest_revision_date)
                {
                    tin_validation_api_latest_revision_id = dataSetRevision.Id;
                    tin_validation_api_latest_revision_date = dataSetRevision.UpdatedAt;
                }
            }
            Console.WriteLine("--> OK found the id of the latest revision of the TIN Validation API: {0}\n", tin_validation_api_latest_revision_id);

            // List the assets of the revison and find the latest one
            ListRevisionAssetsRequest listRevisionAssetsRequest = new ListRevisionAssetsRequest();
            listRevisionAssetsRequest.DataSetId = tin_validation_api_id;
            listRevisionAssetsRequest.RevisionId = tin_validation_api_latest_revision_id;

            Task dataSetAssetsTask = client.ListRevisionAssetsAsync(listRevisionAssetsRequest);
            dataSetAssetsTask.Wait();

            string tin_validation_api_latest_asset_id = System.String.Empty;
            DateTime tin_validation_api_latest_asset_date = new DateTime(2000,01,01);
            foreach (AssetEntry dataSetAsset in dataSetAssetsTask.Result.Assets)
            {
                Console.WriteLine("{0} - [{1}] / [{2}]",
                    dataSetAsset.Id,
                    dataSetAsset.UpdatedAt,
                    dataSetAsset.Name);

                if (dataSetAsset.UpdatedAt > tin_validation_api_latest_asset_date)
                {
                    tin_validation_api_latest_asset_id = dataSetAsset.Id;
                    tin_validation_api_latest_asset_date = dataSetAsset.UpdatedAt;
                }
            }
            Console.WriteLine("--> OK found the id of the latest asset of the TIN Validation API revision: {0}\n", tin_validation_api_latest_asset_id);

            // Call the API
            SendApiAssetRequest apiRequest = new SendApiAssetRequest();
            apiRequest.DataSetId = tin_validation_api_id;
            apiRequest.RevisionId = tin_validation_api_latest_revision_id;
            apiRequest.AssetId = tin_validation_api_latest_asset_id;
            apiRequest.Method = "GET";
            apiRequest.Body = "";

            string tin_country = "US";
            string user_type_id = "I";
            string tin = "123456789";

            string path = $"""/tin_validate/{tin_country}/{user_type_id}/{tin}""";
            Console.WriteLine("Calling OpenTIN API using the path:{0}", path);
            apiRequest.Path = path;

            Task apiResponseTask = client.SendApiAssetAsync(apiRequest);
            apiResponseTask.Wait();

            Console.WriteLine("--> Open TIN API response:{0}", apiResponseTask.Result.Body);
        
        }
        else {
            Console.WriteLine("profile not found in your credentials file");
        }
    }
  }
}
package com.openautomation;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.services.dataexchange.DataExchangeClient;
import software.amazon.awssdk.services.dataexchange.model.*;
import software.amazon.awssdk.regions.Region;

public class OpenTIN_API {
    private static final String profileName = "___AWS_PROFILE_NAME___";
    private static final String regionName = "eu-central-1";

    private static final String RESTAPIHOST = "api-fulfill.dataexchange.eu-central-1.amazonaws.com";
    private static final String RESTAPIPATH = "/v1";

    private static final String METHOD = "POST";
    private static final String SERVICE = "dataexchange";
    private static final String DATA_EXCHANGE_REGION = "eu-central-1";
    private static final String ALGORITHM = "AWS4-HMAC-SHA256";

    private static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) throws NoSuchAlgorithmException {
        byte[] kSecret = ("AWS4" + key).getBytes(StandardCharsets.UTF_8);
        byte[] kDate = hmacSha256(kSecret, dateStamp);
        byte[] kRegion = hmacSha256(kDate, regionName);
        byte[] kService = hmacSha256(kRegion, serviceName);
        return hmacSha256(kService, "aws4_request");
    }

    private static byte[] hmacSha256(byte[] key, String data) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(key, "HmacSHA256"));
            return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            throw new RuntimeException(e);
        }
    }

    private static String hmacSha256Hex(byte[] key, String data) throws NoSuchAlgorithmException {
        return bytesToHex(hmacSha256(key, data));
    }

    private static String sha256Hex(String data) throws NoSuchAlgorithmException {
        return bytesToHex(MessageDigest.getInstance("SHA-256").digest(data.getBytes(StandardCharsets.UTF_8)));
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) result.append(String.format("%02x", b));
        return result.toString();
    }

    private static boolean runningOnAwsMwaa() {
        return System.getenv("AWS_EXECUTION_ENV") != null;
    }

    private static boolean validateTIN(String tinCountry, String userTypeId, String tin) {
        AwsCredentialsProvider credentialsProvider = runningOnAwsMwaa()
            ? DefaultCredentialsProvider.create()
            : ProfileCredentialsProvider.create(profileName);

        DataExchangeClient dataExchangeClient = DataExchangeClient.builder()
            .region(Region.of(regionName))
            .credentialsProvider(credentialsProvider)
            .build();

        AwsCredentials credentials = credentialsProvider.resolveCredentials();
        String AWS_ACCESS_KEY_ID = credentials.accessKeyId();
        String AWS_SECRET_ACCESS_KEY = credentials.secretAccessKey();

        String datasetId = null, revisionId = null, assetId = null;

        try {
            // Find the most recent TIN API dataset
            Instant mostRecent = ZonedDateTime.of(2022, 1, 1, 0, 0, 0, 0, ZoneId.of("Europe/Berlin")).toInstant();
            for (DataSetEntry ds : dataExchangeClient.listDataSets(ListDataSetsRequest.builder().maxResults(200).origin("ENTITLED").build()).dataSets()) {
                if (ds.updatedAt().isAfter(mostRecent) && ds.name().contains("TIN") && ds.name().contains("API")) {
                    datasetId = ds.id();
                    mostRecent = ds.updatedAt();
                }
            }

            // Find the most recent revision
            mostRecent = ZonedDateTime.of(2022, 1, 1, 0, 0, 0, 0, ZoneId.of("Europe/Berlin")).toInstant();
            for (RevisionEntry rev : dataExchangeClient.listDataSetRevisions(ListDataSetRevisionsRequest.builder().dataSetId(datasetId).maxResults(200).build()).revisions()) {
                if (rev.updatedAt().isAfter(mostRecent)) {
                    revisionId = rev.id();
                    mostRecent = rev.updatedAt();
                }
            }

            // Find the most recent asset
            mostRecent = ZonedDateTime.of(2022, 1, 1, 0, 0, 0, 0, ZoneId.of("Europe/Berlin")).toInstant();
            for (AssetEntry asset : dataExchangeClient.listRevisionAssets(ListRevisionAssetsRequest.builder().dataSetId(datasetId).revisionId(revisionId).maxResults(200).build()).assets()) {
                if (asset.updatedAt().isAfter(mostRecent)) {
                    assetId = asset.id();
                    mostRecent = asset.updatedAt();
                }
            }

            // Build the signed request and call the API
            String opentinPath = String.format("/tin_validate/%s/%s/%s", tinCountry, userTypeId, tin);
            System.out.println("Calling OpenTIN API: " + opentinPath);

            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US);
            dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
            String amzDate = dateFormat.format(new Date());
            String dateStamp = amzDate.substring(0, 8);

            String payloadHash = sha256Hex("");
            String canonicalHeaders =
                "content-type:application/json\n" +
                "host:" + RESTAPIHOST + "\n" +
                "x-amz-date:" + amzDate + "\n" +
                "x-amzn-dataexchange-asset-id:" + assetId + "\n" +
                "x-amzn-dataexchange-data-set-id:" + datasetId + "\n" +
                "x-amzn-dataexchange-http-method:GET\n" +
                "x-amzn-dataexchange-path:" + opentinPath + "\n" +
                "x-amzn-dataexchange-revision-id:" + revisionId + "\n";
            String signedHeaders = "content-type;host;x-amz-date;x-amzn-dataexchange-asset-id;x-amzn-dataexchange-data-set-id;x-amzn-dataexchange-http-method;x-amzn-dataexchange-path;x-amzn-dataexchange-revision-id";

            String canonicalRequest = METHOD + "\n" + RESTAPIPATH + "\n\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + payloadHash;
            String credentialScope = String.format("%s/%s/%s/aws4_request", dateStamp, DATA_EXCHANGE_REGION, SERVICE);
            String stringToSign = ALGORITHM + "\n" + amzDate + "\n" + credentialScope + "\n" + sha256Hex(canonicalRequest);

            byte[] signingKey = getSignatureKey(AWS_SECRET_ACCESS_KEY, dateStamp, DATA_EXCHANGE_REGION, SERVICE);
            String signature = hmacSha256Hex(signingKey, stringToSign);
            String authHeader = String.format("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s",
                ALGORITHM, AWS_ACCESS_KEY_ID, credentialScope, signedHeaders, signature);

            URL url = new URL("https://" + RESTAPIHOST + RESTAPIPATH);
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            con.setDoOutput(true);
            con.setRequestMethod(METHOD);
            con.setRequestProperty("content-type", "application/json");
            con.setRequestProperty("host", RESTAPIHOST);
            con.setRequestProperty("x-amz-date", amzDate);
            con.setRequestProperty("x-amzn-dataexchange-asset-id", assetId);
            con.setRequestProperty("x-amzn-dataexchange-data-set-id", datasetId);
            con.setRequestProperty("x-amzn-dataexchange-http-method", "GET");
            con.setRequestProperty("x-amzn-dataexchange-path", opentinPath);
            con.setRequestProperty("x-amzn-dataexchange-revision-id", revisionId);
            con.setRequestProperty("Authorization", authHeader);

            try (OutputStream os = con.getOutputStream()) {
                os.write("".getBytes(StandardCharsets.UTF_8));
            }

            int responseCode = con.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                System.out.println(new String(con.getInputStream().readAllBytes(), StandardCharsets.UTF_8));
            } else {
                System.out.println("Error: " + responseCode + " " + con.getResponseMessage());
            }

            return true;

        } catch (Exception e) {
            System.err.println("EXCEPTION: " + e.getMessage());
            e.printStackTrace();
        }
        return false;
    }

    public static void main(String[] args) throws Exception {
        validateTIN("US", "I", "123456789");
    }
}