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
Bearerfollowed by a space, thenclientID:apiKeyseparated 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:
Body:
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:
- Authorization Header (Recommended, and the way to authenticate GET requests)
- 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:
Invalid Credentials¶
If the provided credentials (header or body) are invalid:
Unauthorized Access¶
If credentials are valid but lack permission for the requested operation:
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:
3. Gradual Migration¶
- Update one endpoint to use headers
- Test thoroughly
- Update remaining endpoints
- 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]