SLTax360 REST API

Send your policies for surplus lines filing, read their status, and calculate the tax.

Base URL (one URL for every endpoint)
https://api.sltax360.com/api/v1
POST /invoices
POST /invoices/preview
GET /invoices/{id}
PUT /invoices/{id}/documents/{docUUID}
GET /auth/me
POST /calculator/estimate
Documentation

Send your first invoice

SLTax360 files surplus lines tax for the policies your system writes.

  • You send each policy transaction from your system: a new policy, a renewal, an endorsement or a cancellation.
  • We prepare and submit the state filing. The licensed broker stays responsible for the filing.
  • You start small. Six fields are enough for the first call. Add more data later.

You need an API key. It looks like sltax_t2_ followed by 32 letters and digits. If you do not have one, get a free test key: our team sets up a separate test account for you. Put your key in an environment variable called SLTAX_API_KEY, so it stays out of your code.

Step 0: check your key
cURL
curl https://api.sltax360.com/api/v1/auth/me \
  -H "Authorization: Bearer $SLTAX_API_KEY"

A 200 reply with your key name and tier means the key works. A 401 means the key is missing or wrong.

Step 1: send an invoice with the 6 required fields
cURL
curl -X POST https://api.sltax360.com/api/v1/invoices \
  -H "Authorization: Bearer $SLTAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "policy_number": "DEMO-0001",
    "insured_name": "Harbor Point Marine LLC",
    "state_code": "TX",
    "net_premium": 12500.00,
    "effective_date": "2026-10-01",
    "expiration_date": "2027-10-01"
  }'

The reply is 201 Created. Your ids and times will be different:

Response: 201 Created
{
  "success": true,
  "data": {
    "id": 12832,
    "policy_number": "DEMO-0001",
    "invoice_number": null,
    "invoice_date": "2026-10-01",
    "insured_name": "Harbor Point Marine LLC",
    "state": { "id": 45, "code": "TX", "name": "Texas" },
    "type": { "id": 1, "name": "New" },
    "lob": null,
    "net_premium": 12500,
    "commission": null,
    "effective_date": "2026-10-01",
    "expiration_date": "2027-10-01",
    "status": "new",
    "filed": false,
    "paid": false,
    "created": "2026-09-23 11:55:21",
    "company": null,
    "policy_id": 6909,
    "policy_fee": 0,
    "stamps_fee": 0,
    "service_fee": 0,
    "notes": null,
    "fees": [],
    "coverages": [],
    "insurers": [],
    "documents": []
  },
  "meta": {
    "timestamp": "2026-09-23T16:55:21Z",
    "request_id": "req_070f04a750ae11ebefd69589",
    "response_time_ms": 9
  }
}
That is a real invoice in your SLTax360 account. Our team can start work on it now. Keep data.id: you use it to read the status and to add documents.
Every create stores an invoice, and a retry makes a second one, unless your account has the new features and you send an Idempotency-Key. Do your tests on a test account: get a free test key. To check a body without storing anything, send it to POST /api/v1/invoices/preview. If a create times out, check first with GET /api/v1/invoices?policy_number=DEMO-0001 before you send it again. That list shows every transaction on the policy, so compare the type, effective_date and net_premium.

The integration ladder

Climb only as far as you need. Each step works on its own.

StepWhat you doWhat you get
0. Hello GET /auth/me with your key. Proof that the key works, with its tier and rate limit.
1. First invoice POST /invoices with the 6 required fields. 201 with an invoice id. Our team does the rest.
2. Send what you have Add the address, coverages, insurers, fees and documents. See Step 2. Fewer questions from our team before we can file.
3. Use our lists Read our reference lists (states, lines of business, coverages, fee types, document types, company search) and send our exact codes and ids. Fewer codes for our team to match by hand. The lists change rarely, so you can cache them.
4. Go further POST /invoices/preview before you create, GET /invoices/{id} for status, PUT /invoices/{id}/documents/{docUUID} for late documents, POST /invoices/batch for bulk. Checks before you send, status read back, and bulk sends (Premium tier).

First invoice in your language

Each example sends the same 6-field invoice and prints the new invoice id. They all read your key from SLTAX_API_KEY.

cURL
curl -X POST https://api.sltax360.com/api/v1/invoices \
  -H "Authorization: Bearer $SLTAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "policy_number": "DEMO-0001",
    "insured_name": "Harbor Point Marine LLC",
    "state_code": "TX",
    "net_premium": 12500.00,
    "effective_date": "2026-10-01",
    "expiration_date": "2027-10-01"
  }'
JavaScript (Node.js 18 or later). Save as first-invoice.mjs, run: node first-invoice.mjs
const response = await fetch("https://api.sltax360.com/api/v1/invoices", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SLTAX_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    policy_number: "DEMO-0001",
    insured_name: "Harbor Point Marine LLC",
    state_code: "TX",
    net_premium: 12500.00,
    effective_date: "2026-10-01",
    expiration_date: "2027-10-01",
  }),
});

const result = await response.json();
if (response.status === 201) {
  console.log("Created invoice", result.data.id, "status:", result.data.status);
} else {
  console.error(response.status, result.error.message, JSON.stringify(result.error.details));
}
Python 3 with requests (pip install requests)
import os
import requests

response = requests.post(
    "https://api.sltax360.com/api/v1/invoices",
    headers={"Authorization": f"Bearer {os.environ['SLTAX_API_KEY']}"},
    json={
        "policy_number": "DEMO-0001",
        "insured_name": "Harbor Point Marine LLC",
        "state_code": "TX",
        "net_premium": 12500.00,
        "effective_date": "2026-10-01",
        "expiration_date": "2027-10-01",
    },
    timeout=30,
)

result = response.json()
if response.status_code == 201:
    print("Created invoice", result["data"]["id"], "status:", result["data"]["status"])
else:
    print(response.status_code, result["error"]["message"], result["error"].get("details"))
PHP 7.4 or later with the curl extension
<?php
$payload = [
    'policy_number'   => 'DEMO-0001',
    'insured_name'    => 'Harbor Point Marine LLC',
    'state_code'      => 'TX',
    'net_premium'     => 12500.00,
    'effective_date'  => '2026-10-01',
    'expiration_date' => '2027-10-01',
];

$ch = curl_init('https://api.sltax360.com/api/v1/invoices');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('SLTAX_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode($payload),
]);
$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$result = json_decode($body, true);

if ($status === 201) {
    echo "Created invoice {$result['data']['id']} status: {$result['data']['status']}\n";
} else {
    echo "$status {$result['error']['message']}\n";
    echo json_encode($result['error']['details'] ?? null), "\n";
}
C# (.NET 8 or later, console app)
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("SLTAX_API_KEY"));

var payload = """
{
  "policy_number": "DEMO-0001",
  "insured_name": "Harbor Point Marine LLC",
  "state_code": "TX",
  "net_premium": 12500.00,
  "effective_date": "2026-10-01",
  "expiration_date": "2027-10-01"
}
""";

var response = await client.PostAsync(
    "https://api.sltax360.com/api/v1/invoices",
    new StringContent(payload, Encoding.UTF8, "application/json"));

var body = await response.Content.ReadAsStringAsync();
using var json = JsonDocument.Parse(body);
if ((int)response.StatusCode == 201)
{
    var data = json.RootElement.GetProperty("data");
    Console.WriteLine($"Created invoice {data.GetProperty("id")} status: {data.GetProperty("status")}");
}
else
{
    Console.WriteLine($"{(int)response.StatusCode} {body}");
}

Step 2: send what you have

All of these fields are optional. Send the ones your system has. The more you send, the less our team must ask you.

FieldWhat it isRule
policy_numberYour policy number (required).Send the same policy_number on every transaction of a policy: the new policy, each endorsement and the cancellation. That is how we link them.
type_codeThe kind of transaction.Exactly NEW, RENEWAL, ENDORSEMENT or CANCELLATION, in capitals. NEW if you leave it out. Any other value is an error. Audits, reinstatements and rewrites cannot be sent through the API yet.
insured_addressWhere the risk is: line1, line2, city, zip.The state comes from state_code. type_id is 2 (garaging, the default) or 1 (billing).
coverages[]Each coverage: code and amount (its premium).Codes differ by state, and GET /coverages does not show each code's state yet. Check your codes for a state with POST /invoices/preview. Our list does not have codes for every state: if no code fits, send your own code and a title. If you also send limits[], their premium values must add up to amount.
insurers[]The insurance companies: company_id and percent or amount.company_id is our id. Find it with GET /companies/search?q= and a name or NAIC code. One insurer with no percent means 100%.
lob_idLine of business.Our id from GET /lobs. An unknown id is an error, so leave it out if you are not sure.
fees[]Fees and taxes you billed: type and amount.Up to 25 items. Codes from GET /reference/fee-types. Create does not calculate the tax. Send the tax you billed as items too, with the code that preview returns in tax_calculation.line_items[].code (for example sl_tax and stamping_fee). To work the figures out first, use POST /invoices/preview.
invoice_number, invoice_dateYour own invoice reference and date.invoice_date is the effective date if you leave it out.
commission, policy_limit, class_codeExtra policy details.Optional. policy_limit cannot be negative. If we hold no such class_code for the state, create stores the invoice without it and returns a warning. Preview does not check class codes yet.
documents[]Files such as the declarations page: type and docUUID.A reference to a file in your own document store, not the file itself. Up to 10 per request. Talk to us first, because we must connect to your document store.

Endorsements and cancellations

  • Send the same policy_number as the original policy, with type_code ENDORSEMENT or CANCELLATION.
  • Send the date the change takes effect as effective_date, and the policy's expiration date as expiration_date. The state filing uses effective_date as the date of the change.
  • Known limit today: the API has one date pair, so the stored policy record takes these dates, and its start date then shows the date of the change. On accounts with the new features, send the policy term instead, and the date of the change in transaction_effective_date.
  • Every amount is the amount of the change, not the policy total: net_premium, coverage amounts, insurer amounts and the tax you billed on the change. Amounts that return premium are negative.
  • Use preview to work out the tax on the change. Unlike the calculator, POST /invoices/preview accepts negative amounts and returns negative tax lines for a return of premium.
  • On read-back, type.name is New, Renewal, Endorsement or Cancelled.
Example body for an endorsement that adds 1,200.00 (send it to POST /api/v1/invoices)
{
  "policy_number": "DEMO-0002",
  "insured_name": "Harbor Point Marine LLC",
  "state_code": "TX",
  "type_code": "ENDORSEMENT",
  "net_premium": 1200.00,
  "effective_date": "2027-01-15",
  "expiration_date": "2027-10-01",
  "coverages": [
    { "code": "9334", "amount": 1200.00 }
  ],
  "insurers": [
    { "company_id": 167, "percent": 100 }
  ],
  "fees": [
    { "type": "sl_tax", "amount": 58.20 },
    { "type": "stamping_fee", "amount": 0.48 }
  ]
}

A cancellation has the same shape, with "type_code": "CANCELLATION" and negative amounts, for example "net_premium": -6250.00.

Example body for step 2 (send it to POST /api/v1/invoices)
{
  "policy_number": "DEMO-0002",
  "insured_name": "Harbor Point Marine LLC",
  "state_code": "TX",
  "type_code": "NEW",
  "net_premium": 12500.00,
  "effective_date": "2026-10-01",
  "expiration_date": "2027-10-01",
  "insured_address": {
    "line1": "100 Harbor Drive",
    "city": "Galveston",
    "zip": "77550"
  },
  "coverages": [
    { "code": "9334", "amount": 12500.00 }
  ],
  "insurers": [
    { "company_id": 167, "percent": 100 }
  ],
  "fees": [
    { "type": "policy_fee_company", "amount": 150.00 }
  ]
}

The company_id above is only an example. Ids belong to your account, so always look them up with GET /companies/search.

What happens to a code we do not know. If a coverage code does not match our list, we keep your original coverage data on the invoice so our team can see it, but the coverage is not saved as a matched coverage, and it is not shown in the API reply. The reply does not warn you about this, unless your account has coverage warnings switched on. To check your codes first, send the body to POST /invoices/preview: it lists each unknown code in validation.errors.

Features on new accounts

These features are optional, and we switch them on per account. New integrations can ask for them when they request a key. On an account without them, the API ignores the extra fields and the header, exactly as described above.

FeatureWhat you sendWhat you get
Safe retriesAn Idempotency-Key header on POST /invoices: your own id for this request, up to 255 characters, no spaces.The same key and the same body within 24 hours return the first 201 again, with the header Idempotent-Replayed: true. No second invoice. The same key with a different body is a 409. A 422 does not use up the key.
Carrier by NAIC or nameinsurers[].naic or insurers[].company_name instead of company_id (also inside groups by line of business).We find the company for you. A company_id, when sent, always wins. No match is a 422 that names the value we looked for.
Line of business by namelob_name, for example "General Liability", when you do not send lob_id.We find the line of business. An unknown name is not an error: the invoice is stored without one, and warnings says so.
Transaction datetransaction_effective_date (YYYY-MM-DD) on an endorsement or cancellation. Keep effective_date and expiration_date as the policy term.We store it as the date of the change, and the policy keeps its real term. The reply shows transaction_effective_date.
The state's answerNothing extra: call GET /api/v1/invoices/{id}.A filing object: state_status (not_sent, sent, accepted, confirmed, rejected or refused), updated_at, confirmation_number, submission_number, and message with the reason for a rejection. The latest answer counts.
Coverage warningsNothing extra.warnings lists each coverage code that we could not match, so you can fix your codes.
Safe retry: send the same request again with the same key
curl -X POST https://api.sltax360.com/api/v1/invoices \
  -H "Authorization: Bearer $SLTAX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: DEMO-0001-new-2026-10-01" \
  -d '{
    "policy_number": "DEMO-0001",
    "insured_name": "Harbor Point Marine LLC",
    "state_code": "TX",
    "net_premium": 12500.00,
    "effective_date": "2026-10-01",
    "expiration_date": "2027-10-01"
  }'
An endorsement with the new fields (send it to POST /api/v1/invoices)
{
  "policy_number": "DEMO-0002",
  "insured_name": "Harbor Point Marine LLC",
  "state_code": "TX",
  "type_code": "ENDORSEMENT",
  "net_premium": 1200.00,
  "effective_date": "2026-10-01",
  "expiration_date": "2027-10-01",
  "transaction_effective_date": "2027-01-15",
  "lob_name": "General Liability",
  "coverages": [
    { "code": "9334", "amount": 1200.00 }
  ],
  "insurers": [
    { "naic": "19437", "percent": 100 }
  ],
  "fees": [
    { "type": "sl_tax", "amount": 58.20 },
    { "type": "stamping_fee", "amount": 0.48 }
  ]
}
Part of the GET /api/v1/invoices/{id} reply, before the state has answered
"filing": {
  "state_status": "not_sent",
  "updated_at": null,
  "confirmation_number": null,
  "submission_number": null,
  "message": null,
  "api_error": null
}

Common errors and the fix

These are the errors partners really hit, taken from our request log. A 422 reply lists the problems in error.details.fields. Insurer and coverage errors are listed under insurers or coverages, and the message names the item, for example insurers[0].

MessageWhat it meansThe fix
insurers[0].amount is required and must be a numberYou grouped insurers by line of business (lob with companies), and a group has no amount.Add amount to every group.
Sum of insurers LOB amounts (10000) does not equal net_premium (12500)The group amounts do not add up to the premium.Make the group amount values add up to net_premium (1 cent tolerance).
coverages[0] (GL): amount 12500.00 does not equal the sum of its limits[].premium (10000.00)You sent limits[], and their premiums do not add up to the coverage amount.Make them add up, or leave out limits[] and send amount only.
insurers[0].company_id is required and must be an integerAn insurer has no company id.Look up the id with GET /companies/search?q= and the company name or NAIC code.
Must be one of: NEW, RENEWAL, ENDORSEMENT, CANCELLATIONtype_code is not one of the four values, or not in capitals.Send one of the four values exactly, or leave it out for NEW.
Referenced line_of_businesses not foundlob_id is not one of our ids.Use an id from GET /lobs, or leave lob_id out.
Must be a valid dateA date is not in YYYY-MM-DD form.Send dates like 2026-10-01.
Must be a valid state_codestate_code is not a US state.Send the 2-letter code, for example TX. DC is also accepted.
This field is requiredOne of the 6 required fields is missing.Send policy_number, insured_name, state_code, net_premium, effective_date and expiration_date.

On POST /invoices/preview, problems with the data itself come back in a 200 reply, inside validation.errors. The most common ones:

Preview messageWhat it meansThe fix
State PA is not supported for filingYour account has no direct electronic filing for this state.Nothing is wrong with your data. POST /invoices still accepts it. Ask us how we file that state for you.
Net premium cannot be zeroThe premium is 0.Send the real premium. POST /invoices does not block a zero premium, so check it on your side.
Coverage code 'GL' not found for state TXThe code is not in our coverage list for that state.Use a code from GET /coverages. Our list does not have codes for every state; if none fits, send your own code and a title. See the note in Step 2.

What happens after you send

  1. The invoice is stored in your SLTax360 account. Our team reviews it and fills any gaps.
  2. We prepare and submit the state filing. Direct electronic filing exists in 25+ states. In other states we prepare the filing for the state's own process. The licensed broker stays responsible for the filing.
  3. You read the status with GET /api/v1/invoices/{id}:
    • status: new, sent, success, error or re-submitted.
    • filed: true once the status is sent, success or re-submitted.
    • paid: always false today. Do not use it yet.
    • created: server time, US Central, without a time zone in the value.
The state's answer: on accounts with the new features, GET /api/v1/invoices/{id} also returns filing with the state's confirmation number or the reason for a rejection. On other accounts these are not returned (ask us, with the invoice id). Not available yet: webhooks (we do not call you when the status changes) and a list of class codes. Read the status on a schedule, for example once a day. For tests, use a free test account, or POST /invoices/preview, which stores nothing (it still counts toward your hourly limit).

Words you will see

You do not need to know insurance to connect. These are the terms the API uses.

Surplus lines
Insurance from a company that is not licensed in the state. It is allowed for risks that licensed companies do not cover. The state taxes it, and each policy must be filed with the state.
Admitted insurer
An insurance company that is licensed in the state. Its policies do not need a surplus lines filing.
Premium
The price of the policy. In the API: net_premium.
Endorsement
A change to a policy during its term, for example a new vehicle. It can add premium or return premium (a negative amount). Send it with type_code ENDORSEMENT.
Renewal and cancellation
A renewal is the next term of a policy (RENEWAL). A cancellation ends a policy early and usually returns premium (CANCELLATION).
NAIC code
A 5-digit number that identifies an insurance company in the US. Use it to find our company_id with GET /companies/search.
Stamping fee
A small fee that some states charge on top of the tax. It pays for the state office that checks the filings.
Line of business
The kind of insurance, for example general liability or commercial auto. In the API: lob_id, from GET /lobs.
Coverage code
A short code for one coverage in the policy. States use it to see what was insured. Codes differ by state.

Tools

Authentication

Send your API key as a Bearer token in the Authorization header of every request.

HTTP Header
Authorization: Bearer sltax_t2_your_api_key_here

Key format and tiers

A key is sltax_, then the tier (t1, t2 or t3), then 32 lowercase letters and digits. Our team issues keys. POST /auth/token only checks a key; it does not create one. Your key also decides which SLTax360 account your data goes to.

TierCalls per hourWhat the key can do
t1 Basic100Read data and preview invoices. It cannot create invoices.
t2 Standard1,000Everything in Basic, plus create, update and delete invoices and documents.
t3 Premium5,000Everything in Standard, plus POST /invoices/batch (up to 500 invoices per call).

The tax calculator (POST /calculator/estimate) also needs the calculator scope on your key. Ask us if you need it.

Security: Never expose API keys in client-side code, public repositories, or browser requests. Always make API calls from your server.

Base URL

Use https://api.sltax360.com/api/v1 for every endpoint: invoices, documents, reference data, key checks and the calculator. Your key selects your account, so you do not need a subdomain of your own.

Always use https. A request to http:// is redirected. When a client follows that redirect, a POST becomes a GET, so your invoice is not sent.

Response Format

All responses use a consistent JSON envelope with success, data/error, and meta fields.

Success Response

Success envelope
{
  "success": true,
  "data": { /* endpoint-specific data */ },
  "meta": {
    "timestamp": "2026-09-23T16:55:21Z",
    "request_id": "req_070f04a750ae11ebefd69589",
    "response_time_ms": 45
  }
}

Error Response

Error envelope
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": { /* field-level errors when applicable */ }
  },
  "meta": { /* same as success */ }
}

Meta Object

FieldDescription
timestampISO 8601 timestamp of the response
request_idUnique request identifier for debugging. Include this in support requests.
response_time_msServer-side processing time in milliseconds

Error Codes

HTTP StatusError CodeDescription
400BAD_REQUESTThe request body is empty or is not valid JSON.
401UNAUTHORIZEDThe API key is missing, wrong, revoked or expired.
403FORBIDDENYour key cannot do this. For example, a Basic (t1) key cannot create invoices, and the calculator needs the calculator scope.
403TIER_REQUIREDThe operation needs a higher tier, for example POST /invoices/batch needs Premium (t3).
404NOT_FOUNDThe resource or the endpoint does not exist.
405METHOD_NOT_ALLOWEDWrong HTTP method, for example POST to /invoices/{id}/documents. Use PUT /invoices/{id}/documents/{docUUID}.
409CONFLICTThe state has accepted the invoice (status success), so it cannot be changed or deleted. Also returned when a company with that name already exists.
422VALIDATION_ERRORThe data failed a rule. Check error.details.fields, and see Common errors.
429RATE_LIMIT_EXCEEDEDToo many calls this hour. Wait the number of seconds in the Retry-After header.
500INTERNAL_ERRORUnexpected server error. Contact support with the request_id.

Validation Error Example

A real reply to POST /invoices with "type_code": "Renewal" and an insurer group without amount:

422 Validation Error
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": {
      "fields": {
        "type_code": ["Must be one of: NEW, RENEWAL, ENDORSEMENT, CANCELLATION"],
        "insurers": ["insurers[0].amount is required and must be a number"]
      }
    }
  },
  "meta": {
    "timestamp": "2026-09-23T16:58:25Z",
    "request_id": "req_747bd15702f4aec469d62a87",
    "response_time_ms": 5
  }
}

On some accounts, our team keeps a rejected invoice so it is not lost. On those accounts the reply also has error.details.invoice_id. When you send the corrected invoice, it replaces the kept one.

Rate Limits

Each key has an hourly limit. The count starts again at the start of each clock hour. Every call counts, including previews and reference lists.

Response Headers

These headers come with every reply to a call that has a valid key.

HeaderDescription
X-RateLimit-LimitMaximum calls allowed per hour
X-RateLimit-RemainingCalls left in the current hour
X-RateLimit-ResetUnix timestamp when the count starts again

Limits by Tier

TierCalls per hour
t1 Basic100
t2 Standard1,000
t3 Premium5,000
Need higher limits? Contact us at support@sltax360.com to discuss custom rate limits for your integration.

Documents

Some states need supporting files before they accept a filing, for example the declarations page or an SL2 form. You do not upload files to us. You send a reference (docUUID) to a file in your own document store. Once we have connected to your document store, we fetch the file from there. Talk to us before you start, because that connection is set up once per partner.

Two ways to send a reference

  • With the invoice: add a documents[] array to POST /invoices.
  • Later: PUT /invoices/{id}/documents/{docUUID}. Use this when a document arrives after the invoice. It also works after the invoice is filed.

Endpoints

MethodPathPurpose
PUT /invoices/{id}/documents/{docUUID} Add a document, or change its type or file name. 201 when it is new, 200 when it already exists. Safe to retry.
GET /invoices/{id}/documents List the documents on an invoice.
GET /invoices/{id}/documents/{doc_id or docUUID} One document, with a fresh download link.
DELETE /invoices/{id}/documents/{doc_id or docUUID} Remove one document. Replies 204 No Content.

Rules

FieldRule
typeRequired. A short code of letters, digits, underscore or hyphen, up to 50 characters. The usual values are declaration_page, sl2_form, lloyds_syndicates, policy_endorsement, quote, policy, invoice, subjectivity and other (see GET /reference/document-types). We store the code in lowercase. Only these types go to the state with a filing: declaration_page, sl2_form, declinations_form, lloyds_syndicates, policy_endorsement and ecp_form. Other codes are kept for our team, but they are not sent to the state.
docUUIDRequired. Your id for the file, up to 256 characters. On the PUT path it is always your docUUID.
filenameOptional, up to 255 characters.
mime_typeOptional: application/pdf, image/png, image/jpeg or image/jpg.
Per requestUp to 10 documents in documents[] on POST /invoices.
Do not put our numeric document id in the PUT path. PUT treats the path value as your docUUID, so a number there creates a new document with that number as its reference. GET and DELETE accept either value. A docUUID made only of digits is read as our id there.

Add a document later: PUT /invoices/{id}/documents/{docUUID}

cURL: PUT a document reference
curl -X PUT "https://api.sltax360.com/api/v1/invoices/12832/documents/9f3c1a2e-5d6b-4a7c-8e9f-0a1b2c3d4e5f" \
  -H "Authorization: Bearer $SLTAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type":      "declaration_page",
    "filename":  "dec-page.pdf",
    "mime_type": "application/pdf"
  }'
Response: 201 Created (200 OK when you send it again)
{
  "success": true,
  "data": {
    "id": 6,
    "doc_type": "declaration_page",
    "file_name": "dec-page.pdf",
    "mime_type": "application/pdf",
    "file_size": null,
    "is_excluded": false,
    "external_doc_uuid": "9f3c1a2e-5d6b-4a7c-8e9f-0a1b2c3d4e5f",
    "fetch_status": "pending",
    "url": null,
    "url_expires_in_seconds": null,
    "added": "2026-09-23 11:58:25"
  },
  "meta": { /* timestamp, request_id, response_time_ms */ }
}

Send references with the invoice

Part of a POST /invoices body
"documents": [
  {
    "type":      "declaration_page",
    "docUUID":   "9f3c1a2e-5d6b-4a7c-8e9f-0a1b2c3d4e5f",
    "filename":  "dec-page.pdf",
    "mime_type": "application/pdf"
  }
]

List, read and delete

cURL: list, read one, delete one
curl "https://api.sltax360.com/api/v1/invoices/12832/documents" \
  -H "Authorization: Bearer $SLTAX_API_KEY"

curl "https://api.sltax360.com/api/v1/invoices/12832/documents/9f3c1a2e-5d6b-4a7c-8e9f-0a1b2c3d4e5f" \
  -H "Authorization: Bearer $SLTAX_API_KEY"

curl -X DELETE "https://api.sltax360.com/api/v1/invoices/12832/documents/9f3c1a2e-5d6b-4a7c-8e9f-0a1b2c3d4e5f" \
  -H "Authorization: Bearer $SLTAX_API_KEY"

Document fields in replies

The same object comes back from GET and PUT, and in the documents[] field of GET /invoices/{id}.

FieldTypeDescription
idintegerOur document id. You can use it on GET and DELETE.
doc_typestringThe type you sent.
file_namestringThe file name you sent, or the name from your document store after the fetch.
mime_typestring or nullThe file type. It can be null until the file is fetched.
file_sizeinteger or nullSize in bytes. Null until the file is fetched.
is_excludedbooleanTrue when our team has set the document aside.
external_doc_uuidstring or nullYour docUUID. Null for files our team added by email or upload.
fetch_statusstringFor your references: pending, fetching, fetched or failed. none for files our team added by email or upload.
urlstring or nullA download link that works for 60 minutes. Null until the file is fetched.
url_expires_in_secondsinteger or null3600 when url is set.
addeddatetimeWhen the document was first recorded.
Do not store the url field. It stops working after one hour. Call GET /invoices/{id}/documents/{docUUID} when you need a fresh link.

Interactive Reference: Invoices and Data

POST /invoices · POST /invoices/preview · GET /invoices/{id} · documents · GET /companies/search · GET /lobs · reference lists

Invoices, documents, policies, insureds, companies and reference data.

Base URL: api.sltax360.com/api/v1. Click Authorize and paste your key, then use Try it out on any endpoint. Every call here is real: a create makes a real invoice. Use POST /invoices/preview to test.

Interactive Reference: Key Check and Calculator

GET /auth/me · POST /auth/token · POST /calculator/estimate

Check your key, and calculate the surplus lines tax and fees for a premium.

Base URL: api.sltax360.com/api/v1, the same as above. Click Authorize and paste your key, then use Try it out on any endpoint.