Authentication
All API requests require a Bearer token in the Authorization header. Tokens are issued per-tenant and scoped to your account.
Authorization: Bearer sltax_t2_your_api_key_here
Token Format
API keys follow the format sltax_t2_<random> where t2 identifies the token version.
One Key, Two Base URLs
Your API key works across both base URLs. Obtain a token from the shared Auth endpoint, then use the same Bearer token for tenant-specific endpoints.
| Scope | Access |
|---|---|
read | Read invoices, companies, policies, insureds, and reference data |
write | Create and update invoices |
calculator | Access the tax calculator endpoint |
Base URLs
The SLTax360 API uses two base URLs. Tenant endpoints (invoices, policies, companies, insureds, reference data) are served from your tenant subdomain. Shared endpoints (authentication, tax calculator) are served from the central API domain.
| Base URL | Endpoints | Description |
|---|---|---|
your-tenant.sltax360.com/api/v1 |
Invoices, Companies, Policies, Insureds, Reference Data | Tenant-specific data (reads/writes your tenant database) |
api.sltax360.com/api/v1 |
Auth, Calculator | Shared services (authentication tokens, tax estimation) |
Quick Start
Submit an invoice to the API with a single request:
curl -X GET "https://your-tenant.sltax360.com/api/v1/invoices" \
-H "Authorization: Bearer sltax_t2_your_api_key_here" \
-H "Content-Type: application/json"
const response = await fetch('https://your-tenant.sltax360.com/api/v1/invoices', {
headers: {
'Authorization': 'Bearer sltax_t2_your_api_key_here',
'Content-Type': 'application/json',
},
});
const result = await response.json();
if (result.success) {
console.log('Invoices:', result.data);
} else {
console.error('Error:', result.error.message);
}
import requests
response = requests.get(
"https://your-tenant.sltax360.com/api/v1/invoices",
headers={
"Authorization": "Bearer sltax_t2_your_api_key_here",
"Content-Type": "application/json",
},
)
result = response.json()
if result["success"]:
for inv in result["data"]:
print(f"Invoice #{inv['id']}: {inv['invoice_number']}")
else:
print(f"Error: {result['error']['message']}")
Response Format
All responses use a consistent JSON envelope with success, data/error, and meta fields.
Success Response
{
"success": true,
"data": { /* endpoint-specific data */ },
"meta": {
"timestamp": "2026-01-19T12:00:00Z",
"request_id": "req_abc123",
"response_time_ms": 45
}
}
Error Response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": { /* field-level errors when applicable */ }
},
"meta": { /* same as success */ }
}
Meta Object
| Field | Description |
|---|---|
timestamp | ISO 8601 timestamp of the response |
request_id | Unique request identifier for debugging. Include this in support requests. |
response_time_ms | Server-side processing time in milliseconds |
Error Codes
| HTTP Status | Error Code | Description |
|---|---|---|
400 | BAD_REQUEST | Malformed request body or parameters |
401 | UNAUTHORIZED | Missing or invalid API key |
403 | FORBIDDEN | API feature not enabled for your plan |
404 | NOT_FOUND | Resource or endpoint not found |
405 | METHOD_NOT_ALLOWED | Wrong HTTP method for this endpoint |
422 | VALIDATION_ERROR | Request parameters failed validation. Check details.fields. |
429 | RATE_LIMIT_EXCEEDED | Too many requests. Retry after the period in the response. |
500 | INTERNAL_ERROR | Unexpected server error. Contact support with request_id. |
Validation Error Example
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": {
"fields": {
"invoice_number": ["is required"],
"state_id": ["must be a valid state ID"]
}
}
}
}
Rate Limits
API requests are rate-limited per API key on a per-hour basis. Limits vary by plan tier.
Response Headers
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed per hour |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp when the limit resets |
Limits by Plan
| Plan | Requests / Hour |
|---|---|
| Professional | 500 |
| Enterprise | 5,000 |
Attaching Documents to Invoices
Documents are supporting files (declarations page, SL2 form, Lloyd's syndicates list, policy endorsement, quote, other) that some states require at submission time. Documents are stored in tenant S3 and returned with 60-minute pre-signed URLs.
Two ways to send the bytes
Each document carries its bytes in exactly one of two ways (sending both, or neither, is a 422):
base64— inline bytes. Decoded, mime/size-checked and stored synchronously; the response carries a usableurlright away.docUUID— a reference to a file in your external NGIN blob storage. The document is recorded immediately and the bytes are pulled asynchronously from NGIN by a background job. The row starts atfetch_status: "pending"with a nullurl/file_sizeand flips to"fetched"once the file lands in S3. When usingdocUUID,filenameandmime_typeare optional — NGIN supplies them at fetch time.
Two delivery paths
You can attach documents inline at create by including a documents[] array on POST /invoices — one atomic round trip. Or you can use the post-create sub-resource below at any time, including after state submission. Post-create attachment is the right path when documents arrive late (auditor follow-ups, post-bind endorsements).
Endpoints
| Method | Path | Purpose |
|---|---|---|
| POST | /invoices/{id}/documents |
Append one or more documents |
| GET | /invoices/{id}/documents |
List documents on an invoice |
| GET | /invoices/{id}/documents/{doc_id} |
Fetch a single document with a fresh pre-signed URL |
| PUT | /invoices/{id}/documents/{doc_id} |
Replace bytes and/or update metadata |
| DELETE | /invoices/{id}/documents/{doc_id} |
Remove a single document |
Limits and allowed values
| Constraint | Value |
|---|---|
| Max documents per request | 10 |
| Max size per document (decoded) | 10 MB |
| Max combined size per request | 25 MB |
Allowed type values | declaration_page, sl2_form, lloyds_syndicates, policy_endorsement, quote, other |
Allowed mime_type values | application/pdf, image/png, image/jpeg, image/jpg |
status_id = 3). This is the only post-submit mutation the API allows, and is what lets you attach a dec page returned by an auditor or a post-bind endorsement to an already-filed invoice.
1. Append a document — POST /invoices/{id}/documents
Send one or more documents in a single call. Returns the newly-created rows with 60-minute pre-signed URLs.
curl -X POST "https://your-tenant.sltax360.com/api/v1/invoices/12345/documents" \
-H "Authorization: Bearer sltax_t2_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"documents": [
{
"type": "declaration_page",
"filename": "dec.pdf",
"mime_type": "application/pdf",
"base64": "JVBERi0xLjQK..."
}
]
}'
{
"success": true,
"data": {
"documents": [
{
"id": 501,
"doc_type": "declaration_page",
"file_name": "dec.pdf",
"mime_type": "application/pdf",
"file_size": 184392,
"is_excluded": false,
"external_doc_uuid": null,
"fetch_status": "none",
"url": "https://s3.amazonaws.com/...&X-Amz-Expires=3600&...",
"url_expires_in_seconds": 3600,
"added": "2026-05-19 09:14:22"
}
]
}
}
By reference (NGIN docUUID). Send docUUID instead of base64 — no bytes in the request. The document is recorded as pending and pulled from NGIN asynchronously; poll GET until fetch_status is fetched for a usable url.
curl -X POST "https://your-tenant.sltax360.com/api/v1/invoices/12345/documents" \
-H "Authorization: Bearer sltax_t2_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"documents": [
{
"type": "declaration_page",
"docUUID": "00788bf-b724-417d-a73b-2d3b5b09a94d"
}
]
}'
2. List documents — GET /invoices/{id}/documents
Returns every invoice_files row tied to the invoice, regardless of source (api, email, manual). Each item carries a fresh 60-minute pre-signed S3 URL.
curl "https://your-tenant.sltax360.com/api/v1/invoices/12345/documents" \
-H "Authorization: Bearer sltax_t2_your_api_key_here"
3. Get a single document — GET /invoices/{id}/documents/{doc_id}
Use this when a previously-fetched pre-signed URL has expired and you need a fresh one without re-listing every document on the invoice.
curl "https://your-tenant.sltax360.com/api/v1/invoices/12345/documents/501" \
-H "Authorization: Bearer sltax_t2_your_api_key_here"
url field. It is a 60-minute pre-signed S3 URL — it will stop working after one hour. Re-fetch via GET /invoices/{id}/documents/{doc_id} whenever you need a fresh URL.
4. Update a document — PUT /invoices/{id}/documents/{doc_id}
The {doc_id} path segment may be either our numeric document id or your NGIN docUUID (for documents you attached by reference). The endpoint supports three modes, selected by which field you send (base64 and docUUID are mutually exclusive). All fields are optional; an empty body returns 422.
4a. Replace the file bytes. When base64 is present, the document is re-uploaded to a new S3 key, the row is updated, and the old S3 object is removed best-effort. You may also send new type, filename, or mime_type in the same call.
curl -X PUT "https://your-tenant.sltax360.com/api/v1/invoices/12345/documents/501" \
-H "Authorization: Bearer sltax_t2_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"type": "declaration_page",
"filename": "dec-corrected.pdf",
"mime_type": "application/pdf",
"base64": "JVBERi0xLjQK..."
}'
4b. Update metadata only. Omit base64 and the row is updated in place — no S3 traffic. Send any combination of type, filename, and is_excluded.
curl -X PUT "https://your-tenant.sltax360.com/api/v1/invoices/12345/documents/501" \
-H "Authorization: Bearer sltax_t2_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"type": "policy_endorsement",
"filename": "endorsement-2.pdf"
}'
4c. Re-point a NGIN reference. Send a new docUUID and the document resets to fetch_status: "pending"; any previously fetched file is removed and the background job re-pulls the new document from NGIN. You may also send a new type. Address the document by its current docUUID or numeric id.
curl -X PUT "https://your-tenant.sltax360.com/api/v1/invoices/12345/documents/00788bf-b724-417d-a73b-2d3b5b09a94d" \
-H "Authorization: Bearer sltax_t2_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"type": "declaration_page",
"docUUID": "9f3c1a2e-5d6b-4a7c-8e9f-0a1b2c3d4e5f"
}'
5. Delete a document — DELETE /invoices/{id}/documents/{doc_id}
Hard-deletes the row and removes the underlying S3 object best-effort. Responds 204 No Content on success.
curl -X DELETE "https://your-tenant.sltax360.com/api/v1/invoices/12345/documents/501" \
-H "Authorization: Bearer sltax_t2_your_api_key_here"
DocumentResponse shape
The same object is returned by GET, POST, and PUT (and by the documents[] field on GET /invoices/{id}).
| Field | Type | Description |
|---|---|---|
id | integer | Document row id (the numeric {doc_id} path segment) |
doc_type | string | One of the allowed type values |
file_name | string | Caller-supplied filename, or NGIN's filename for references |
mime_type | string or null | Mime verified against the file's magic bytes; null for a reference until fetched |
file_size | integer or null | Decoded byte count of the stored file; null until fetched |
is_excluded | boolean | Soft-exclude flag; documents marked excluded are kept but hidden from internal UI |
external_doc_uuid | string or null | Your NGIN docUUID when the document was attached by reference; null otherwise |
fetch_status | string | none (inline base64), or for references: pending/fetching (in flight), fetched (in S3), failed (auto-retried) |
url | string or null | 60-minute pre-signed S3 URL; null when S3 is not configured or a reference is not yet fetched |
url_expires_in_seconds | integer or null | Always 3600 when url is set |
added | datetime | When the document row was created (immutable) |
Interactive Tenant API Reference
POST /invoices · GET /invoices · GET /companies · GET /policies · GET /insureds
Tenant-specific endpoints for managing invoices, companies, policies, insureds, and reference data.
{tenant_slug}.sltax360.com/api/v1 — Click Authorize to enter your Bearer token, then Try it out on any endpoint.