Skip to content

Authorization headers

Benefits of Authorization Header

  • Improved Security: Credentials are separated from business data, reducing accidental exposure when sharing request examples
  • Cleaner Request Bodies: Keep your request payloads focused on business logic only
  • Industry Standard: Aligns with TransactAPI best practices
  • Debugging Safety: Share request bodies with team members without worrying about credential exposure

Authorization Header

Required Header Format

TAPI uses the standard Authorization header with a Bearer token format:

Header Format Example
Authorization Bearer {clientID}:{apiKey} Authorization: Bearer YOUR_CLIENT_ID:YOUR_API_KEY

Important Notes

  • The format is Bearer followed by a space, then clientID:apiKey separated by a colon
  • Header names are case-insensitive (per HTTP specification)
  • The Authorization header takes precedence over body parameters if both are provided
  • The Bearer token must contain both clientID and apiKey separated by a colon (:)
  • Authenticate GET requests with the Authorization header - GET has no request body, and credentials in the URL end up in server and proxy logs

Migration Guide

Before (Body Authentication)

{
  "developerAPIKey": "YOUR_API_KEY",
  "clientID": "YOUR_CLIENT_ID",
  "accountId": "A12345",
  "amount": 1000
}

After (Authorization Header)

Headers:

Authorization: Bearer YOUR_CLIENT_ID:YOUR_API_KEY
Content-Type: application/json

Body:

{
  "accountId": "A12345",
  "amount": 1000
}

GET Endpoints

GET requests have no body, so send credentials in the Authorization header:

curl -X GET "$TAPI_HOST/v3/parties" \
  -H "Authorization: Bearer $TAPI_CLIENT_ID:$TAPI_API_KEY"

# These endpoints include: parties, entities, trades, offerings, issuers, links, exports, jobs, permissions

Implementation Examples

cURL

curl -X POST "$TAPI_HOST/v3/createTrade" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TAPI_CLIENT_ID:$TAPI_API_KEY" \
  -d '{
    "accountId": "A12345",
    "offeringId": "O54321",
    "transactionType": "ACH",
    "transactionUnits": "100"
  }'

JavaScript (Fetch API)

const response = await fetch(`${process.env.TAPI_HOST}/v3/createTrade`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${process.env.TAPI_CLIENT_ID}:${process.env.TAPI_API_KEY}`
  },
  body: JSON.stringify({
    accountId: 'A12345',
    offeringId: 'O54321',
    transactionType: 'ACH',
    transactionUnits: '100'
  })
});

JavaScript (Axios)

const axios = require('axios');

const response = await axios.post(
  `${process.env.TAPI_HOST}/v3/createTrade`,
  {
    accountId: 'A12345',
    offeringId: 'O54321',
    transactionType: 'ACH',
    transactionUnits: '100'
  },
  {
    headers: {
      'Authorization': `Bearer ${process.env.TAPI_CLIENT_ID}:${process.env.TAPI_API_KEY}`
    }
  }
);

Python (Requests)

import os
import requests

headers = {
    'Content-Type': 'application/json',
    'Authorization': f"Bearer {os.environ['TAPI_CLIENT_ID']}:{os.environ['TAPI_API_KEY']}"
}

data = {
    'accountId': 'A12345',
    'offeringId': 'O54321',
    'transactionType': 'ACH',
    'transactionUnits': '100'
}

response = requests.post(
    f"{os.environ['TAPI_HOST']}/v3/createTrade",
    headers=headers,
    json=data
)

PHP (cURL)

$ch = curl_init(getenv('TAPI_HOST') . '/v3/createTrade');

$headers = [
    'Content-Type: application/json',
    'Authorization: Bearer ' . getenv('TAPI_CLIENT_ID') . ':' . getenv('TAPI_API_KEY'),
];

$data = [
    'accountId' => 'A12345',
    'offeringId' => 'O54321',
    'transactionType' => 'ACH',
    'transactionUnits' => '100'
];

curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);

C# (.NET HttpClient)

using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;

var host = Environment.GetEnvironmentVariable("TAPI_HOST");
var clientId = Environment.GetEnvironmentVariable("TAPI_CLIENT_ID");
var apiKey = Environment.GetEnvironmentVariable("TAPI_API_KEY");

var client = new HttpClient();

client.DefaultRequestHeaders.Add("Authorization", $"Bearer {clientId}:{apiKey}");

var data = new
{
    accountId = "A12345",
    offeringId = "O54321",
    transactionType = "ACH",
    transactionUnits = "100"
};

var json = JsonSerializer.Serialize(data);
var content = new StringContent(json, Encoding.UTF8, "application/json");

var response = await client.PostAsync($"{host}/v3/createTrade", content);

Java (OkHttp)

import okhttp3.*;

OkHttpClient client = new OkHttpClient();

String json = "{\"accountId\":\"A12345\",\"offeringId\":\"O54321\",\"transactionType\":\"ACH\",\"transactionUnits\":\"100\"}";

RequestBody body = RequestBody.create(
    json, 
    MediaType.parse("application/json")
);

Request request = new Request.Builder()
    .url(System.getenv("TAPI_HOST") + "/v3/createTrade")
    .addHeader("Content-Type", "application/json")
    .addHeader("Authorization", "Bearer " + System.getenv("TAPI_CLIENT_ID")
        + ":" + System.getenv("TAPI_API_KEY"))
    .post(body)
    .build();

Response response = client.newCall(request).execute();

Ruby (Net::HTTP)

require 'net/http'
require 'json'

uri = URI("#{ENV['TAPI_HOST']}/v3/createTrade")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = "Bearer #{ENV['TAPI_CLIENT_ID']}:#{ENV['TAPI_API_KEY']}"

request.body = {
  accountId: 'A12345',
  offeringId: 'O54321',
  transactionType: 'ACH',
  transactionUnits: '100'
}.to_json

response = http.request(request)

Backwards Compatibility

Both Methods Supported

Both authentication methods are supported:

  1. Authorization Header (Recommended, and the way to authenticate GET requests)
  2. Body Parameters (POST/PUT/PATCH/DELETE only, not available for GET)

Method Availability by HTTP Verb

HTTP Method Header Auth Body Auth Notes
GET ✅ Use this ❌ N/A No request body in GET requests
POST ✅ Supported ✅ Supported Header auth recommended
PUT ✅ Supported ✅ Supported Header auth recommended
PATCH ✅ Supported ✅ Supported Header auth recommended
DELETE ✅ Supported ✅ Supported Header auth recommended

Priority Order

If credentials are provided in both headers and body (POST, PUT, PATCH or DELETE):

  • Headers take precedence
  • Body credentials are ignored

Example with Both

# Authorization header will be used, body credentials ignored
curl -X POST "$TAPI_HOST/v3/getAccount" \
  -H "Authorization: Bearer $TAPI_CLIENT_ID:$TAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "developerAPIKey": "old_key_ignored",
    "clientID": "old_id_ignored",
    "accountId": "A12345"
  }'

Error Handling

Missing Headers

If authentication headers are missing and no body credentials are provided:

{
  "statusCode": 103,
  "statusDesc": "Invalid developer API Key"
}

Invalid Credentials

If the provided credentials (header or body) are invalid:

{
  "statusCode": 103,
  "statusDesc": "Invalid developer API Key"
}

Unauthorized Access

If credentials are valid but lack permission for the requested operation:

{
  "statusCode": 110,
  "statusDesc": "You do not have the required permissions"
}

Best Practices

1. Environment Variables

Store credentials in environment variables, not in code:

// Good
const apiKey = process.env.TAPI_API_KEY;
const clientId = process.env.TAPI_CLIENT_ID;

// Bad
const apiKey = 'YOUR_API_KEY'; // Never hardcode a real key

2. Configuration Management

Create a centralized configuration for API calls:

// config.js
module.exports = {
  baseURL: `${process.env.TAPI_HOST}/v3`,
  headers: {
    'Authorization': `Bearer ${process.env.TAPI_CLIENT_ID}:${process.env.TAPI_API_KEY}`
  }
};

// usage.js
const config = require('./config');
const axios = require('axios');

const client = axios.create({
  baseURL: config.baseURL,
  headers: config.headers
});

// Now all requests automatically include auth headers
const response = await client.post('/createTrade', {
  accountId: 'A12345',
  offeringId: 'O54321',
  transactionType: 'ACH',
  transactionUnits: '100'
});

3. Error Handling

Always handle authentication errors gracefully:

try {
  const response = await makeApiCall();
  // Process successful response
} catch (error) {
  if (error.response?.status === 401) {
    console.error('Authentication failed. Check your API credentials.');
  } else if (error.response?.status === 403) {
    console.error('Permission denied. Your API key may lack required scopes.');
  } else {
    console.error('API call failed:', error.message);
  }
}

4. Logging and Debugging

When logging requests, exclude sensitive headers:

// Create a sanitized request log
function logRequest(config) {
  const sanitized = {
    ...config,
    headers: {
      ...config.headers,
      'Authorization': 'Bearer ***REDACTED***'
    }
  };
  console.log('API Request:', sanitized);
}

Testing Your Implementation

1. Test Environment

Start with the sandbox environment:

  • Sandbox URL: https://api-sandboxdash.norcapsecurities.com
  • Use your sandbox credentials

2. Verify Headers Are Sent

Use a tool like curl with -v flag to see headers:

curl -X GET "$TAPI_HOST/v3/ping" \
  -v \
  -H "Authorization: Bearer $TAPI_CLIENT_ID:$TAPI_API_KEY"

3. Gradual Migration

  1. Update one endpoint to use headers
  2. Test thoroughly
  3. Update remaining endpoints
  4. Remove body credentials once all endpoints are updated

Frequently Asked Questions

Q: Is Authorization header authentication available in all environments?

A: Yes. The Authorization header is supported in every environment, sandbox and production.

Q: Will my existing integrations break?

A: No. Body-based authentication continues to work for POST, PUT, PATCH and DELETE requests.

Q: Can I use body authentication on GET methods?

A: No. GET requests have no body. Authenticate GET calls with the Authorization header.

Q: Can I use different authentication methods for different endpoints?

A: You can, since each request is authenticated independently, but it is not recommended. Use the Authorization header for all requests.

Q: Is the header name case-sensitive?

A: No. HTTP headers are case-insensitive. Authorization, authorization, and AUTHORIZATION all work.

Q: What if I accidentally send credentials in both headers and body?

A: Headers take precedence. Body credentials will be ignored if headers are present.

Q: Do webhook endpoints require these headers?

A: No. Webhook endpoints (callbacks from external services like DocuSign) have their own authentication mechanisms.

Support

For questions or issues with header authentication:

  • Technical Support: techsupport@northcapital.com
  • API Documentation: [https\://api-sandboxdash.norcapsecurities.com/documentation]
  • Status Page: [https\://status.northcapital.com]