> This page is for version v6.0 (default).
> For other versions, use one of these documentation indexes:
> - v6.0 (default): https://docs.zip.tax/v-6-0/llms.txt
> - v5.0: https://docs.zip.tax/v-5-0/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.zip.tax/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.zip.tax/_mcp/server.

# Nexus Management

Nexus management lets your platform record each self-managed merchant's physical
locations on Ziptax. Those locations are what establish the merchant's nexus, and
nexus is what decides whether a sale is sourced at its origin or its destination
— so this is the data a
[self-managed cart calculation](self-managed-cart-calculation) depends on.

This applies to [self-managed merchants](self-managed-merchants) only. A
[TaxCloud-connected merchant's](taxcloud-connected-merchants) nexus is held in
TaxCloud, where TaxCloud manages it as part of end-to-end compliance; every
endpoint on this page returns `403` for one.

## Why nexus matters

A merchant must collect sales tax in every state where it has **nexus**, a
connection to the state significant enough to create a tax obligation. The
footprint you record does two jobs:

1. **It defines where the merchant collects.** Ziptax uses it as the record of
   the merchant's collection obligations.
2. **It drives correct calculation.** Some states source sales tax to the
   ship-from location (origin-based) rather than the ship-to location
   (destination-based), so calculating the right rate depends on knowing where
   the merchant ships from.

Nexus comes in two kinds:

| Kind                  | How it arises                                                                | Recorded how                                                                           |
| --------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| **Physical presence** | An office, store, warehouse, inventory, employee, or other in-state presence | Declared by you, through the endpoints on this page                                    |
| **Economic nexus**    | Sales into a state cross that state's sales or transaction threshold         | Derived from sales activity, not declared. See [economic nexus](#economic-nexus) below |

See [Collection Requirements and Nexus](../reference/collection-requirements-and-nexus)
for background on the legal concepts, and
[Economic Thresholds](economic-thresholds) for how state thresholds work.

## The location object

Record every place the merchant has a physical presence in the US: offices,
stores, warehouses, inventory (including inventory held in fulfillment networks
such as FBA), employees, property, and temporary presence such as trade shows.

Only `locationType` and `address` are required. Everything else is optional and
defaulted.

| Field                 | Type    | Required | Description                                                                                                                            |
| --------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `locationType`        | string  | Yes      | What kind of presence this is. One of the [seven types](#location-types).                                                              |
| `address`             | string  | Yes      | The location's US address, as a single line. Max 255 characters. Geocoded on write.                                                    |
| `registered`          | boolean | No       | Whether the merchant is registered to collect sales tax in this location's state. Defaults to `false`.                                 |
| `isFulfillmentOrigin` | boolean | No       | Whether goods actually ship from here. This is the field origin-sourcing consults. [Defaults by `locationType`](#fulfillment-origin).  |
| `name`                | string  | No       | Your own label, for example `TestCo Texas Distribution Center`. Max 255 characters. Returned as-is and never used in a calculation.    |
| `referenceId`         | string  | No       | The ID you use for this location in your own system, for example `warehouse-123`. Max 255 characters.                                  |
| `locationId`          | string  | —        | Server-issued on create and returned on every read, for example `P-6dc371de-00a4-44bf-8a88-2bcab2f15041`. Use it to update and delete. |

### Location types

| `locationType` | Covers                                                                      |
| -------------- | --------------------------------------------------------------------------- |
| `office`       | Corporate, administrative, or branch offices                                |
| `store`        | Retail storefronts                                                          |
| `warehouse`    | Storage facilities holding the merchant's inventory                         |
| `fulfillment`  | Third-party fulfillment centers, including marketplace networks such as FBA |
| `personnel`    | Employees or contractors working in a state                                 |
| `property`     | Inventory or equipment held in a state                                      |
| `temporary`    | Short-term presence such as a trade show                                    |

### Fulfillment origin

`isFulfillmentOrigin` is the field origin-sourcing actually reads, and when you
omit it Ziptax infers it from `locationType`:

| `locationType`                                 | Default `isFulfillmentOrigin` |
| ---------------------------------------------- | ----------------------------- |
| `warehouse`, `fulfillment`, `store`            | `true`                        |
| `office`, `personnel`, `property`, `temporary` | `false`                       |

> **Warning**
>
> Set `isFulfillmentOrigin` explicitly for anything that does not match the
> default — an office that also ships, or a warehouse that only holds stock and
> never ships. Leaving it to the default in those cases changes which address an
> origin-sourced sale is calculated from.

### How addresses are handled

You send the address the way your merchant gave it to you, as a single line.
Ziptax geocodes it on write and returns the normalized result, so the address you
read back will not be byte-identical to the one you sent:

| Direction | Value                                                  |
| --------- | ------------------------------------------------------ |
| Sent      | `1401 Lavaca St Austin TX 78701`                       |
| Returned  | `1401 Lavaca St, Austin, TX 78701-1634, United States` |

US addresses only. An address that cannot be resolved to a state is rejected
with `422` rather than stored. Match on `locationId` or `referenceId` rather
than on the address string.

## Create locations

### Request

POST [https://api.zip-tax.com/merchant/nexus/create](https://api.zip-tax.com/merchant/nexus/create)

```curl
curl -X POST https://api.zip-tax.com/merchant/nexus/create \
     -H "X-API-KEY: <apiKey>" \
     -H "Content-Type: application/json" \
     -d '{
  "locations": [
    {
      "address": "1401 Lavaca St Austin TX 78701",
      "locationType": "office"
    }
  ],
  "merchantId": "merchantId"
}'
```

```python
import requests

url = "https://api.zip-tax.com/merchant/nexus/create"

payload = {
    "locations": [
        {
            "address": "1401 Lavaca St Austin TX 78701",
            "locationType": "office"
        }
    ],
    "merchantId": "merchantId"
}
headers = {
    "X-API-KEY": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.zip-tax.com/merchant/nexus/create';
const options = {
  method: 'POST',
  headers: {'X-API-KEY': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"locations":[{"address":"1401 Lavaca St Austin TX 78701","locationType":"office"}],"merchantId":"merchantId"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.zip-tax.com/merchant/nexus/create"

	payload := strings.NewReader("{\n  \"locations\": [\n    {\n      \"address\": \"1401 Lavaca St Austin TX 78701\",\n      \"locationType\": \"office\"\n    }\n  ],\n  \"merchantId\": \"merchantId\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("X-API-KEY", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.zip-tax.com/merchant/nexus/create")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-API-KEY"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"locations\": [\n    {\n      \"address\": \"1401 Lavaca St Austin TX 78701\",\n      \"locationType\": \"office\"\n    }\n  ],\n  \"merchantId\": \"merchantId\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.zip-tax.com/merchant/nexus/create")
  .header("X-API-KEY", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"locations\": [\n    {\n      \"address\": \"1401 Lavaca St Austin TX 78701\",\n      \"locationType\": \"office\"\n    }\n  ],\n  \"merchantId\": \"merchantId\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.zip-tax.com/merchant/nexus/create', [
  'body' => '{
  "locations": [
    {
      "address": "1401 Lavaca St Austin TX 78701",
      "locationType": "office"
    }
  ],
  "merchantId": "merchantId"
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'X-API-KEY' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.zip-tax.com/merchant/nexus/create");
var request = new RestRequest(Method.POST);
request.AddHeader("X-API-KEY", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"locations\": [\n    {\n      \"address\": \"1401 Lavaca St Austin TX 78701\",\n      \"locationType\": \"office\"\n    }\n  ],\n  \"merchantId\": \"merchantId\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "X-API-KEY": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "locations": [
    [
      "address": "1401 Lavaca St Austin TX 78701",
      "locationType": "office"
    ]
  ],
  "merchantId": "merchantId"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.zip-tax.com/merchant/nexus/create")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

`create` takes an array, so you can register a merchant's whole footprint in one
call. Up to **25 locations per call** — each one is geocoded as it is written,
which is what bounds the batch.

#### cURL

```bash
curl -X POST "https://api.zip-tax.com/merchant/nexus/create" \
  -H "X-API-KEY: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": "dc835fea-b9fb-4f5e-a253-b896455d673f",
    "locations": [{
      "locationType": "warehouse",
      "name": "TestCo Texas Distribution Center",
      "address": "1401 Lavaca St Austin TX 78701",
      "registered": true,
      "referenceId": "warehouse-123"
    }]
  }'
```

The response returns the stored locations in the order submitted, each with its
`locationId` and normalized address:

```json
{
  "merchantId": "dc835fea-b9fb-4f5e-a253-b896455d673f",
  "locations": [{
    "locationId": "P-6dc371de-00a4-44bf-8a88-2bcab2f15041",
    "locationType": "warehouse",
    "name": "TestCo Texas Distribution Center",
    "address": "1401 Lavaca St, Austin, TX 78701-1634, United States",
    "registered": true,
    "isFulfillmentOrigin": true,
    "referenceId": "warehouse-123"
  }]
}
```

Persist the `locationId` values. They are the handle for every later update or
delete.

### The batch is all-or-nothing

If any address in the request cannot be resolved to a US state, the whole request
is rejected with `422` and **nothing is stored**. That is deliberate: it means a
retry cannot duplicate the locations that would otherwise have succeeded, so you
can fix the bad address and resend the same payload safely.

> **Warning**
>
> Address resolution for the whole request is budgeted at **10 seconds**. A batch
> that exceeds it returns `504` and stores nothing — but geocoding that had already
> completed is still billed, because the lookups were still performed. Send fewer
> locations per call if you hit this.

## List the footprint

### Request

POST [https://api.zip-tax.com/merchant/nexus/list](https://api.zip-tax.com/merchant/nexus/list)

```curl
curl -X POST https://api.zip-tax.com/merchant/nexus/list \
     -H "X-API-KEY: <apiKey>" \
     -H "Content-Type: application/json" \
     -d '{
  "merchantId": "merchantId"
}'
```

```python
import requests

url = "https://api.zip-tax.com/merchant/nexus/list"

payload = { "merchantId": "merchantId" }
headers = {
    "X-API-KEY": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.zip-tax.com/merchant/nexus/list';
const options = {
  method: 'POST',
  headers: {'X-API-KEY': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"merchantId":"merchantId"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.zip-tax.com/merchant/nexus/list"

	payload := strings.NewReader("{\n  \"merchantId\": \"merchantId\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("X-API-KEY", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.zip-tax.com/merchant/nexus/list")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-API-KEY"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"merchantId\": \"merchantId\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.zip-tax.com/merchant/nexus/list")
  .header("X-API-KEY", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"merchantId\": \"merchantId\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.zip-tax.com/merchant/nexus/list', [
  'body' => '{
  "merchantId": "merchantId"
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'X-API-KEY' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.zip-tax.com/merchant/nexus/list");
var request = new RestRequest(Method.POST);
request.AddHeader("X-API-KEY", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"merchantId\": \"merchantId\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "X-API-KEY": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = ["merchantId": "merchantId"] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.zip-tax.com/merchant/nexus/list")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

Send `merchantId` alone to get the merchant's whole footprint. Deleted locations
are not included, and an empty array means no physical nexus is being tracked for
that merchant.

#### cURL

```bash
curl -X POST "https://api.zip-tax.com/merchant/nexus/list" \
  -H "X-API-KEY: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": "dc835fea-b9fb-4f5e-a253-b896455d673f"
  }'
```

```json
{
  "merchantId": "dc835fea-b9fb-4f5e-a253-b896455d673f",
  "locations": [{
    "locationId": "P-6dc371de-00a4-44bf-8a88-2bcab2f15041",
    "locationType": "warehouse",
    "name": "TestCo Texas Distribution Center",
    "address": "1401 Lavaca St, Austin, TX 78701-1634, United States",
    "registered": true,
    "isFulfillmentOrigin": true,
    "referenceId": "warehouse-123"
  }]
}
```

The optional `nexusType` filter accepts `physical` or `economic`. Omit it to
return every kind.

## Update a location

### Request

POST [https://api.zip-tax.com/merchant/nexus/update](https://api.zip-tax.com/merchant/nexus/update)

```curl
curl -X POST https://api.zip-tax.com/merchant/nexus/update \
     -H "X-API-KEY: <apiKey>" \
     -H "Content-Type: application/json" \
     -d '{
  "location": {
    "address": "1401 Lavaca St Austin TX 78701",
    "locationType": "office"
  },
  "locationId": "6dc371de-00a4-44bf-8a88-2bcab2f15041",
  "merchantId": "merchantId"
}'
```

```python
import requests

url = "https://api.zip-tax.com/merchant/nexus/update"

payload = {
    "location": {
        "address": "1401 Lavaca St Austin TX 78701",
        "locationType": "office"
    },
    "locationId": "6dc371de-00a4-44bf-8a88-2bcab2f15041",
    "merchantId": "merchantId"
}
headers = {
    "X-API-KEY": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.zip-tax.com/merchant/nexus/update';
const options = {
  method: 'POST',
  headers: {'X-API-KEY': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"location":{"address":"1401 Lavaca St Austin TX 78701","locationType":"office"},"locationId":"6dc371de-00a4-44bf-8a88-2bcab2f15041","merchantId":"merchantId"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.zip-tax.com/merchant/nexus/update"

	payload := strings.NewReader("{\n  \"location\": {\n    \"address\": \"1401 Lavaca St Austin TX 78701\",\n    \"locationType\": \"office\"\n  },\n  \"locationId\": \"6dc371de-00a4-44bf-8a88-2bcab2f15041\",\n  \"merchantId\": \"merchantId\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("X-API-KEY", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.zip-tax.com/merchant/nexus/update")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-API-KEY"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"location\": {\n    \"address\": \"1401 Lavaca St Austin TX 78701\",\n    \"locationType\": \"office\"\n  },\n  \"locationId\": \"6dc371de-00a4-44bf-8a88-2bcab2f15041\",\n  \"merchantId\": \"merchantId\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.zip-tax.com/merchant/nexus/update")
  .header("X-API-KEY", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"location\": {\n    \"address\": \"1401 Lavaca St Austin TX 78701\",\n    \"locationType\": \"office\"\n  },\n  \"locationId\": \"6dc371de-00a4-44bf-8a88-2bcab2f15041\",\n  \"merchantId\": \"merchantId\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.zip-tax.com/merchant/nexus/update', [
  'body' => '{
  "location": {
    "address": "1401 Lavaca St Austin TX 78701",
    "locationType": "office"
  },
  "locationId": "6dc371de-00a4-44bf-8a88-2bcab2f15041",
  "merchantId": "merchantId"
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'X-API-KEY' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.zip-tax.com/merchant/nexus/update");
var request = new RestRequest(Method.POST);
request.AddHeader("X-API-KEY", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"location\": {\n    \"address\": \"1401 Lavaca St Austin TX 78701\",\n    \"locationType\": \"office\"\n  },\n  \"locationId\": \"6dc371de-00a4-44bf-8a88-2bcab2f15041\",\n  \"merchantId\": \"merchantId\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "X-API-KEY": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "location": [
    "address": "1401 Lavaca St Austin TX 78701",
    "locationType": "office"
  ],
  "locationId": "6dc371de-00a4-44bf-8a88-2bcab2f15041",
  "merchantId": "merchantId"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.zip-tax.com/merchant/nexus/update")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

Identify the location with `locationId` at the top level, and send its new
contents in a `location` object.

> **Warning**
>
> `location` is a **full replacement, not a patch**. Every field in it is written,
> so send the current value for anything you do not want cleared — omitting `name`
> or `referenceId` empties them, and omitting `registered` resets it to `false`.
> Read the location first if you do not already hold its current values.

#### cURL

```bash
curl -X POST "https://api.zip-tax.com/merchant/nexus/update" \
  -H "X-API-KEY: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": "dc835fea-b9fb-4f5e-a253-b896455d673f",
    "locationId": "P-6dc371de-00a4-44bf-8a88-2bcab2f15041",
    "location": {
      "locationType": "warehouse",
      "name": "TestCo Texas Distribution Center",
      "address": "1401 Lavaca St Austin TX 78701",
      "registered": true,
      "referenceId": "warehouse-123"
    }
  }'
```

The response returns the stored location as a single `location` object, not an
array:

```json
{
  "merchantId": "dc835fea-b9fb-4f5e-a253-b896455d673f",
  "location": {
    "locationId": "P-6dc371de-00a4-44bf-8a88-2bcab2f15041",
    "locationType": "warehouse",
    "name": "TestCo Texas Distribution Center",
    "address": "1401 Lavaca St, Austin, TX 78701-1634, United States",
    "registered": true,
    "isFulfillmentOrigin": true,
    "referenceId": "warehouse-123"
  }
}
```

Two things to expect. The address is geocoded again, so the normalized result can
change even when you send the same string — and a location can be moved to a
different state this way. And a `locationId` that has already been deleted
returns `404`.

The most common update is flipping `registered` once a merchant completes a state
registration. Because the block is a full replacement, send the location's other
current values alongside it.

## Delete a location

### Request

POST [https://api.zip-tax.com/merchant/nexus/delete](https://api.zip-tax.com/merchant/nexus/delete)

```curl
curl -X POST https://api.zip-tax.com/merchant/nexus/delete \
     -H "X-API-KEY: <apiKey>" \
     -H "Content-Type: application/json" \
     -d '{
  "locationId": "6dc371de-00a4-44bf-8a88-2bcab2f15041",
  "merchantId": "merchantId"
}'
```

```python
import requests

url = "https://api.zip-tax.com/merchant/nexus/delete"

payload = {
    "locationId": "6dc371de-00a4-44bf-8a88-2bcab2f15041",
    "merchantId": "merchantId"
}
headers = {
    "X-API-KEY": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.zip-tax.com/merchant/nexus/delete';
const options = {
  method: 'POST',
  headers: {'X-API-KEY': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"locationId":"6dc371de-00a4-44bf-8a88-2bcab2f15041","merchantId":"merchantId"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.zip-tax.com/merchant/nexus/delete"

	payload := strings.NewReader("{\n  \"locationId\": \"6dc371de-00a4-44bf-8a88-2bcab2f15041\",\n  \"merchantId\": \"merchantId\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("X-API-KEY", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.zip-tax.com/merchant/nexus/delete")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-API-KEY"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"locationId\": \"6dc371de-00a4-44bf-8a88-2bcab2f15041\",\n  \"merchantId\": \"merchantId\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.zip-tax.com/merchant/nexus/delete")
  .header("X-API-KEY", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"locationId\": \"6dc371de-00a4-44bf-8a88-2bcab2f15041\",\n  \"merchantId\": \"merchantId\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.zip-tax.com/merchant/nexus/delete', [
  'body' => '{
  "locationId": "6dc371de-00a4-44bf-8a88-2bcab2f15041",
  "merchantId": "merchantId"
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'X-API-KEY' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.zip-tax.com/merchant/nexus/delete");
var request = new RestRequest(Method.POST);
request.AddHeader("X-API-KEY", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"locationId\": \"6dc371de-00a4-44bf-8a88-2bcab2f15041\",\n  \"merchantId\": \"merchantId\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "X-API-KEY": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "locationId": "6dc371de-00a4-44bf-8a88-2bcab2f15041",
  "merchantId": "merchantId"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.zip-tax.com/merchant/nexus/delete")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

#### cURL

```bash
curl -X POST "https://api.zip-tax.com/merchant/nexus/delete" \
  -H "X-API-KEY: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": "dc835fea-b9fb-4f5e-a253-b896455d673f",
    "locationId": "P-6dc371de-00a4-44bf-8a88-2bcab2f15041"
  }'
```

```json
{
  "merchantId": "dc835fea-b9fb-4f5e-a253-b896455d673f",
  "locationId": "P-6dc371de-00a4-44bf-8a88-2bcab2f15041",
  "message": "location removed"
}
```

The location stops appearing in `list` immediately and no longer contributes to
the merchant's nexus. It is retained internally so past filings keep their basis.
Deleting a location that is already gone returns `404`.

There is no minimum. Removing a merchant's last location is allowed — it simply
means no physical nexus is tracked for them. Deleting a location that still
reflects a real presence does not end the merchant's obligation in that state, so
keep the footprint current.

## Economic nexus

Economic nexus is **derived from a merchant's sales activity rather than
declared**, so it cannot be created through these endpoints. `nexusType` on
`create` accepts only `physical`; sending `economic` is rejected rather than
silently stored as physical.

On `list`, `nexusType: "economic"` is accepted and currently returns an empty
list, because economic nexus is not yet tracked.

Ziptax publishes the per-state threshold reference data, but does not monitor a
merchant's sales against it. See [Economic Thresholds](economic-thresholds) for
how the thresholds are constructed and where to find the data.

## Errors

| Status | Meaning                                                                                      |
| ------ | -------------------------------------------------------------------------------------------- |
| `400`  | Malformed request body.                                                                      |
| `401`  | Missing or invalid API key.                                                                  |
| `403`  | The merchant is TaxCloud-connected. These endpoints are for self-managed merchants only.     |
| `404`  | The `locationId` does not exist, or has already been deleted.                                |
| `422`  | An address could not be resolved to a US state. On `create`, nothing in the batch is stored. |
| `429`  | Rate limit exceeded. See [Rate Limiting & Errors](../reference/rate-limiting-errors).        |
| `504`  | On `create`, the batch exceeded the 10-second address-resolution budget. Nothing is stored.  |

## Related

#### [Self-Managed Merchants](self-managed-merchants)

The compliance model where your platform tracks nexus and the merchant handles their own filing.

#### [Self-Managed Cart Calculation](self-managed-cart-calculation)

The calculation that consults the nexus footprint you record here.

#### [Economic Thresholds](economic-thresholds)

How state sales and transaction thresholds work, with per-state reference data.

#### [Collection Requirements and Nexus](../reference/collection-requirements-and-nexus)

Background on nexus and when a seller must collect sales tax.