Full Workflow
We strongly recommend you download the Developer Integration Kit prior to development, which will walk you through the full workflow below.
Access the Complete Developer Integration Kit
The full implementation kit includes the ready-to-run Postman collection, sample applications in multiple languages, session recording examples, and an AI IDE copilot.
Clients can download the complete kit from the Client Support Center.
New to Symmetry? Contact us to request access and speak with our team.
Step 1: Authenticate
Obtain an access token using your API key. All TLA API requests require a bearer token in the Authorization header:
Authentication Request
GET https://api.symmetry.com/authentication/login
Headers:
| Header | Value |
|---|---|
Accept | application/json |
api-key | <your-symmetry-api-key> |
Authentication Response
{
"accessToken": "eyJhbGciOiJSUzI1NiIs..."
}
The accessToken field contains your Bearer token. Use it in subsequent requests:
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Full Authentication Examples Available
Production-ready authentication examples (including token refresh handling and environment setup) are available in the Developer Integration Kit.
Log in to download the complete samples, or contact us to request access.
Step 2: Determine Your STE Integration Type
How does your system call STE to calculate taxes?
How do you call STE?
ā
āāāāāāāāāāāāāā“āāāāāāāāāāāāā
ā¼ ā¼
JSON API On-Prem SDK
(HTTP/REST) (C, Java, or C#)
ā ā
ā¼ ā¼
You already have Use Session Recording
the PayCalc JSON to capture STE calls
ā go to Path A ā go to Path B
Need Help Choosing the Right Path?
The Integration Kit includes guided examples for both JSON API and On-Prem SDK implementations, including complete working projects.
Clients can access the full walkthrough in the Developer Hub. Prospects can connect with our team for guided onboarding.
| Integration Type | What You Have | Recommended Approach |
|---|---|---|
| STE JSON API | Employee PayCalc data | Use the flat employee object directly as context.payCalc ā simplest path |
| STE On-Prem SDK | Individual SDK API calls (C/Java/C#) | Implement Session Recording ā wraps your existing code with start/stop calls (Path B) |
Step 3: Obtain the Payroll Context
Path A: You Use the STE JSON API
This is the simplest path. You already have the employee PayCalc data that you send to STE. You just need to capture and store the flat employee object.
What to do:
- When your system calls STE via the JSON API, save the flat employee PayCalc object (without the
PayCalcRequestwrapper) - Associate it with the employee, pay period, and pay date for later retrieval
- When a user clicks to explain a tax, retrieve that saved PayCalc object
Complete JSON API Samples Available
Our Client Developer Hub includes full, runnable JSON API implementations with database storage examples and error handling patterns.
Log in to download the full samples, or request access to get started.
Path B: You Use the STE On-Premise SDK (C/Java/C#)
When you use the on-premise SDK, you call individual setter functions rather than sending a single JSON object. To provide context to TLA, capture those calls using Session Recording.
Session Recording captures every STE API call you make and packages it into a compact, replayable base64 string. It requires no restructuring of your existing code ā just wrap your calculation with start/stop calls.
Session Recording Deep Dive
Detailed SDK documentation, edge-case handling, and full language-specific projects are available in the Client Support Center.
Clients can log in for full access. New to Symmetry? Contact us to request implementation support.
Step 4: Associate a UI Action with a Tax ID
The Tax ID identifies which specific tax you want explained. You need to connect a user's UI click to the correct Tax ID.
Tax ID Format
Symmetry Tax ID Guidance
Clients can find simple guidance on all Symmetry Tax IDs in our Developer Hub.
New to Symmetry? Contact us to learn more about how the engine calculates taxes using the Symmetry Tax ID.
Step 5: Construct the TLA API Request
The TLA API has one main endpoint:
POST https://api.symmetry.com/tla/v1/explain
There are two request formats depending on whether you have JSON or a session recording.
Format 1: Using Flat PayCalc (STE JSON API users)
Send the flat employee PayCalc object directly.
{
"taxIds": [
"00-000-0000-FIT-000"
],
"context": {
"payCalc": {
"employeeID": "employee 1",
"payrollRunParameters": {
"payDate": "2026-01-01",
"payPeriodsPerYear": 52,
"payPeriodNumber": 0
},
"wages": [
{
"locationCode": "00-000-0000",
"wageType": "Regular",
"calcMethodRegularWages": "Annualized",
"calcMethodSupplementalWages": "None",
"hours": 40,
"grossWages": 1000,
"mtdWages": 0,
"qtdWages": 0,
"ytdWages": 0
}
],
"taxJurisdictionParms": [
{
"uniqueTaxID": "00-000-0000-FIT-000",
"locationCode": "00-000-0000",
"isExempt": false,
"isResident": true,
"periodWH": 0,
"mtdWH": 0,
"qtdWH": 0,
"ytdWH": 0,
"roundResult": "Yes",
"autoAdjust": false,
"miscParameters": [
{ "parmName": "FILINGSTATUS", "parmValue": "S" },
{ "parmName": "2020_W4", "parmValue": "TRUE" }
]
}
]
}
}
}
taxIds
taxIdsaccepts a single string ("00-000-0000-FIT-000") or an array of up to 30 tax IDs. The example above shows a multi-tax request matching the Postman collection.
Format 2: Using Session Recording (On-Prem SDK users)
{
"taxIds": ["00-000-0000-FIT-000"],
"context": {
"session": "eJx9jEEKg0AMRfc5RtZ/5Cc6DroShAEX3Ulv4AXE3r..."
}
}
Ready-to-Run Postman Collection
A complete Postman collection with preconfigured environments and example requests is available in the Developer Integration Kit.
Log in to download it, or request access to begin testing immediately.
Step 6: Call the TLA API
Full Example (Authentication + Explain)
# 1. Authenticate
TOKEN=$(curl -s https://api.symmetry.com/authentication/login \
-H "Accept: application/json" \
-H "api-key: YOUR_API_KEY" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['accessToken'])")
# 2. Call Explain Tax
curl -s -X POST https://api.symmetry.com/tla/v1/explain \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"taxIds": ["00-000-0000-FICA-000", "00-000-0000-MEDI-000", "00-000-0000-FIT-000"],
"context": {
"payCalc": {
"employeeID": "employee 1",
"payrollRunParameters": {
"payDate": "2020-01-01",
"payPeriodsPerYear": 52,
"payPeriodNumber": 0
},
"wages": [{
"locationCode": "00-000-0000",
"wageType": "Regular",
"calcMethodRegularWages": "Annualized",
"hours": 40,
"grossWages": 1000,
"ytdWages": 0
}],
"taxJurisdictionParms": [
{
"uniqueTaxID": "00-000-0000-FICA-000",
"locationCode": "00-000-0000",
"isExempt": false,
"isResident": true,
"miscParameters": []
},
{
"uniqueTaxID": "00-000-0000-MEDI-000",
"locationCode": "00-000-0000",
"isExempt": false,
"isResident": true,
"miscParameters": []
},
{
"uniqueTaxID": "00-000-0000-FIT-000",
"locationCode": "00-000-0000",
"isExempt": false,
"isResident": true,
"miscParameters": [
{"parmName": "FILINGSTATUS", "parmValue": "S"},
{"parmName": "2020_W4", "parmValue": "TRUE"}
]
}
]
}
}
}' | python3 -m json.tool
const AUTH_URL = 'https://api.symmetry.com';
const TLA_URL = 'https://api.symmetry.com/tla';
class TlaApiClient {
private apiKey: string;
private bearerToken: string | null = null;
constructor(apiKey: string) {
this.apiKey = apiKey;
}
async authenticate(): Promise<void> {
const response = await fetch(`${AUTH_URL}/authentication/login`, {
headers: {
'Accept': 'application/json',
'api-key': this.apiKey,
},
});
if (!response.ok) throw new Error(`Auth failed: ${response.status}`);
const data = await response.json();
this.bearerToken = data.accessToken || data.token || data.access_token;
}
async explainTax(
taxIds: string | string[],
context: { payCalc: object } | { session: string }
): Promise<TlaResponse> {
if (!this.bearerToken) await this.authenticate();
const response = await fetch(`${TLA_URL}/v1/explain`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.bearerToken}`,
},
body: JSON.stringify({ taxIds, context }),
});
if (response.status === 401) {
await this.authenticate();
return this.explainTax(taxIds, context);
}
if (!response.ok) {
const error = await response.json();
throw new Error(`TLA API error ${response.status}: ${JSON.stringify(error)}`);
}
return response.json();
}
}
interface TlaResponse {
id: string;
summary: string | null;
results: Array<{
taxId: string;
response?: string;
usage?: { input_tokens: number; output_tokens: number };
error?: string;
}>;
}
import java.net.http.*;
import java.net.URI;
public class TlaApiClient {
private static final String AUTH_URL = "https://api.symmetry.com";
private static final String TLA_URL = "https://api.symmetry.com/tla";
private final String apiKey;
private final HttpClient httpClient;
private String bearerToken;
public TlaApiClient(String apiKey) {
this.apiKey = apiKey;
this.httpClient = HttpClient.newHttpClient();
}
public void authenticate() throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(AUTH_URL + "/authentication/login"))
.header("Accept", "application/json")
.header("api-key", apiKey)
.GET()
.build();
HttpResponse<String> response = httpClient.send(
request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("Auth failed: " + response.statusCode());
}
// Parse token (use your preferred JSON library)
// bearerToken = parsed.accessToken || parsed.token;
}
public String explainTaxWithSession(String taxIds, String sessionString)
throws Exception {
if (bearerToken == null) authenticate();
String body = String.format(
"{\"taxIds\": \"%s\", \"context\": {\"session\": \"%s\"}}",
taxIds, sessionString);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(TLA_URL + "/v1/explain"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + bearerToken)
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = httpClient.send(
request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 401) {
authenticate();
return explainTaxWithSession(taxIds, sessionString);
}
if (response.statusCode() != 200) {
throw new RuntimeException("TLA API error: " + response.body());
}
return response.body();
}
public String explainTaxWithPayCalc(String taxIds, String payCalcJson)
throws Exception {
if (bearerToken == null) authenticate();
String body = String.format(
"{\"taxIds\": \"%s\", \"context\": {\"payCalc\": %s}}",
taxIds, payCalcJson);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(TLA_URL + "/v1/explain"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + bearerToken)
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = httpClient.send(
request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("TLA API error: " + response.body());
}
return response.body();
}
}
using System.Net.Http;
using System.Text.Json;
public class TlaApiClient
{
private const string AuthUrl = "https://api.symmetry.com";
private const string TlaUrl = "https://api.symmetry.com/tla";
private readonly HttpClient _httpClient = new();
private readonly string _apiKey;
private string? _bearerToken;
public TlaApiClient(string apiKey) => _apiKey = apiKey;
public async Task AuthenticateAsync()
{
var request = new HttpRequestMessage(HttpMethod.Get,
$"{AuthUrl}/authentication/login");
request.Headers.Add("Accept", "application/json");
request.Headers.Add("api-key", _apiKey);
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
_bearerToken = json.RootElement.GetProperty("accessToken").GetString();
}
public async Task<TlaResponse> ExplainTaxAsync(string taxIds, string sessionString)
{
if (_bearerToken is null) await AuthenticateAsync();
var body = new { taxIds, context = new { session = sessionString } };
var content = new StringContent(
JsonSerializer.Serialize(body), System.Text.Encoding.UTF8, "application/json");
var request = new HttpRequestMessage(HttpMethod.Post, $"{TlaUrl}/v1/explain");
request.Headers.Add("Authorization", $"Bearer {_bearerToken}");
request.Content = content;
var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
await AuthenticateAsync();
return await ExplainTaxAsync(taxIds, sessionString);
}
response.EnsureSuccessStatusCode();
return JsonSerializer.Deserialize<TlaResponse>(
await response.Content.ReadAsStringAsync())!;
}
}
public record TlaResponse(string Id, string? Summary, List<TlaTaxResult> Results);
public record TlaTaxResult(string TaxId, string? Response, UsageInfo? Usage, string? Error);
public record UsageInfo(int InputTokens, int OutputTokens);
Complete Runnable Clients Included
Full, production-ready client implementations (Node, Java, C#, and more) are available in the Developer Integration Kit, along with environment configuration and error handling examples.
Access the kit in the Client Support Center or contact us to request access.
Step 7: Process the Response
Success Response (HTTP 200)
The response contains a results array with one entry per requested tax ID:
{
"id": "01JCPMF5BQ812345",
"summary": "Your paycheck has three federal withholdings: $76.50 for Social Security (6.2% of $1,000 gross), $14.50 for Medicare (1.45% of $1,000 gross), and $78.00 for Federal Income Tax based on your Single filing status.",
"results": [
{
"taxId": "00-000-0000-FICA-000",
"response": "Your Social Security (FICA) withholding for this paycheck is $76.50. This is calculated at 6.2% of your $1,000 gross wages...",
"usage": { "input_tokens": 1250, "output_tokens": 110 }
},
{
"taxId": "00-000-0000-MEDI-000",
"response": "Your Medicare withholding for this paycheck is $14.50. This is calculated at 1.45% of your $1,000 gross wages...",
"usage": { "input_tokens": 1250, "output_tokens": 95 }
},
{
"taxId": "00-000-0000-FIT-000",
"response": "Your Federal Income Tax withholding for this paycheck is $78.00. This was calculated using your $1,000 gross pay and your Single filing status...",
"usage": { "input_tokens": 1250, "output_tokens": 125 }
}
]
}
Advanced Response Handling & Monitoring
The Integration Kit includes examples for caching, logging, usage tracking, and production monitoring.
Clients can log in for full implementation guidance. Prospects can connect with our team to learn more.
What the Response Text Looks Like
The response field always follows this structure:
- Opening sentence: States the tax type and the final calculated amount
- Explanation: 2ā3 sentences explaining how the amount was derived, in plain English
- Summary line: Simple math notation showing the key numbers
The text:
- Uses plain English (no payroll jargon)
- Translates technical terms (e.g., "taxable wages" becomes "the amount your tax is based on")
- Is 80ā120 words ā concise enough for a tooltip or modal
- Contains no markdown or HTML ā safe to render as plain text
Step 8: Display the Explanation in Your UI
The response text is designed for direct display. Here are recommended UI patterns:
Pattern 1: Inline Expandable (Recommended)
Best for pay stub views where space is limited.
Federal Income Tax $287.50 [ā¼ Why this amount?]
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Your Federal Income Tax withholding for this ā
ā paycheck is $287.50. This was calculated using ā
ā your $3,000 gross pay and your Single filing ā
ā status. The system projects your annual income ā
ā based on 26 pay periods to determine the correct ā
ā withholding amount from the federal tax tables. ā
ā ā
ā Summary: $3,000 biweekly Ć 26 periods = ā
ā $78,000 projected annual ā $287.50 withheld. ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Pattern 2: Modal / Dialog
Best for mobile or when you want more visual emphasis.
Pattern 3: Tooltip / Popover
Best for dense data tables where minimal disruption is needed.
UI Implementation Tips
- Show a loading state ā The API typically responds in 2ā4 seconds. Show a spinner or "Analyzing..." text.
- Cache explanations ā For the same paycheck, the explanation won't change. Cache client-side or server-side.
- No parsing needed ā The response is plain text. Render it directly.
- Lazy load ā Don't call the API until the user actually clicks "Explain".
- Save the
idā Store the responseidfor support/debugging purposes.
Ready to Implement?
Download the complete Developer Integration Kit to stand up a working TLA API integration in minutes.
Clients: Log in to the Client Support Center.
New to Symmetry? Contact us to request access and schedule a technical walkthrough.
Updated about 3 hours ago