# References API

> Get regions, comunas and your services, and quote a shipment before you create it.

Source: https://api-doc.4nortes.app/api/references

Fetch the same regions, comunas and services you see in your panel, and check coverage and price before creating an order. Use this to build address selectors and shipping options in your checkout.

> **Note:**
>
> Regions and localities rarely change. Fetch them once and cache them for a day. Services and quotes are account-specific — see [Caching](#caching).

---

<a id="list-regions"></a>

## Get regions

`GET /regions`

Get all Chilean regions for your address selector. Returns the full list, sorted by name — no pagination.

### Response Fields

- `id` (integer): Unique region identifier.
- `name` (string): Region name.
- `slug` (string): URL-friendly region identifier.
- `localities_count` (integer): Number of localities in this region.

```bash
curl https://api.4nortes.app/api/v1/regions \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"
```

```js
const response = await fetch(
  'https://api.4nortes.app/api/v1/regions',
  {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Accept': 'application/json',
    },
  }
)
const data = await response.json()
```

```json
{
  "success": true,
  "message": "Regions retrieved successfully",
  "data": [
    {
      "id": 1,
      "name": "Arica y Parinacota",
      "slug": "arica-y-parinacota",
      "localities_count": 4
    },
    {
      "id": 7,
      "name": "Metropolitana de Santiago",
      "slug": "metropolitana-de-santiago",
      "localities_count": 52
    },
    {
      "id": 2,
      "name": "Tarapacá",
      "slug": "tarapaca",
      "localities_count": 7
    },
    {
      "id": 6,
      "name": "Valparaíso",
      "slug": "valparaiso",
      "localities_count": 38
    }
  ],
  "timestamp": "2025-02-03T10:00:00.000000Z"
}
```

---

<a id="list-localities"></a>

## Get comunas

`GET /localities`

Get all comunas (localities), sorted by name. Returns the full list — no pagination.

### Response Fields

- `id` (integer): Unique locality identifier. You can send it (as a string) in `destination_locality` when creating orders.
- `name` (string): Locality name (comuna). Also accepted in `destination_locality`.
- `slug` (string): URL-friendly locality identifier.
- `type` (string): `urban`, `rural` or `both`.
- `region_id` (integer): ID of the region this locality belongs to.
- `aliases` (array): Alternative spellings for this locality. They are also matched when you send a name.

```bash
curl https://api.4nortes.app/api/v1/localities \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"
```

```js
const response = await fetch(
  'https://api.4nortes.app/api/v1/localities',
  {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Accept': 'application/json',
    },
  }
)
const data = await response.json()
```

```json
{
  "success": true,
  "message": "Localities retrieved successfully",
  "data": [
    {
      "id": 3,
      "name": "Las Condes",
      "slug": "las-condes",
      "type": "urban",
      "region_id": 7,
      "aliases": []
    },
    {
      "id": 2,
      "name": "Providencia",
      "slug": "providencia",
      "type": "urban",
      "region_id": 7,
      "aliases": []
    },
    {
      "id": 1,
      "name": "Santiago",
      "slug": "santiago",
      "type": "urban",
      "region_id": 7,
      "aliases": ["Santiago Centro"]
    }
  ],
  "timestamp": "2025-02-03T10:00:00.000000Z"
}
```

---

<a id="list-services"></a>

## Get services

`GET /services`

Get the delivery services configured for **your** account. Only active services are returned, sorted by name, with no pagination. Codes and names are set up per account by 4N NextDay, so they differ from one merchant to another.

Merchant token only: any other token gets a `401` with `Authentication required.`.

### Response Fields

- `id` (integer): Unique service identifier. Use this as `service_id` when creating orders.
- `code` (string): Short code, specific to your account.
- `name` (string): Service name, as shown in your panel.
- `type` (string): `b2b` (deliveries to businesses), `b2c` (deliveries to consumers) or `field` (cargo created by drivers on the road — never used for integration orders).

```bash
curl https://api.4nortes.app/api/v1/services \
  -H "Authorization: Bearer {token}" \
  -H "Accept: application/json"
```

```js
const response = await fetch(
  'https://api.4nortes.app/api/v1/services',
  {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Accept': 'application/json',
    },
  }
)
const data = await response.json()
```

```json
{
  "success": true,
  "message": "Services retrieved successfully",
  "data": [
    {
      "id": 31,
      "code": "B2B-RM",
      "name": "Distribución B2B Región Metropolitana",
      "type": "b2b"
    },
    {
      "id": 27,
      "code": "ECOM-STGO",
      "name": "E-commerce Santiago",
      "type": "b2c"
    }
  ],
  "timestamp": "2025-02-03T10:00:00.000000Z"
}
```

---

<a id="quote-rates"></a>

## Quote a rate

`POST /rates/quote`

Check coverage and price **before** creating an order. The quote resolves the locality and applies your account's coverage rules exactly like `POST /orders/create` does, so a `200` here means the locality will be accepted at creation time. Use it in your checkout to show the shipping cost and to stop uncovered addresses early.

### Request Body

- `destination_locality` (string): The destination locality (comuna): its name, one of its aliases, or its numeric id **as a string** (`"2"`). Name matching ignores case and accents. Max 100 characters. **Required.**
- `weight` (number): Total weight in kilograms. Between `0.1` and `100`. **Required.**
- `packages_count` (integer): Number of packages. Between `1` and `50`. Default `1`.

### Response Fields

- `locality_id` (integer): The locality that matched. Store it and send it back as `destination_locality` to avoid any ambiguity.
- `locality_name` (string): Its canonical name.
- `region_id` (integer): Its region.
- `price` (integer): Total price, in whole pesos.
- `currency` (string): Always `CLP`.
- `transit_days` (integer): Estimated transit time to that locality, in days. At least `1`.
- `breakdown` (object): `base_price` and `weight_surcharge` (both integers, in pesos). They add up to `price`.
- `delivery_message` (string|null): A note about deliveries to that locality for today's weekday (for example a cut-off or frequency notice), when one is configured. `null` otherwise. Show it to the customer as is.

### Errors

- `404 Not Found`: `Locality not found: {input}` — nothing matched the name or id; order creation would fail the same way. Or `No tariff configured for locality: {name}` — the locality exists and may well be covered, but has no price yet. This one only affects the quote: `POST /orders/create` doesn't check tariffs, so don't block checkout on it; contact your account representative to get the price configured.
- `422 Unprocessable Entity`: Envelope with `Merchant {name} has no coverage for locality: {name}` when your account doesn't deliver there. Or a raw validation body when `destination_locality`, `weight` or `packages_count` is missing or out of range.

```bash
curl -X POST https://api.4nortes.app/api/v1/rates/quote \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "destination_locality": "Providencia",
    "weight": 3.0,
    "packages_count": 1
  }'
```

```js
const response = await fetch(
  'https://api.4nortes.app/api/v1/rates/quote',
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    },
    body: JSON.stringify({
      destination_locality: 'Providencia',
      weight: 3.0,
      packages_count: 1,
    }),
  }
)
const data = await response.json()
```

```json
{
  "success": true,
  "message": "Operation successful",
  "data": {
    "locality_id": 2,
    "locality_name": "Providencia",
    "region_id": 7,
    "price": 5500,
    "currency": "CLP",
    "transit_days": 1,
    "breakdown": {
      "base_price": 4000,
      "weight_surcharge": 1500
    },
    "delivery_message": null
  },
  "timestamp": "2025-02-03T10:00:00.000000Z"
}
```

```json
{
  "success": false,
  "message": "Merchant Acme Store has no coverage for locality: Putre",
  "errors": null,
  "timestamp": "2025-02-03T10:00:00.000000Z"
}
```

```json
{
  "success": false,
  "message": "Locality not found: Providencai",
  "errors": null,
  "timestamp": "2025-02-03T10:00:00.000000Z"
}
```

---

<a id="caching"></a>

## Cache this data

| Data       | Cache Duration          | Notes                                                             |
| ---------- | ----------------------- | ----------------------------------------------------------------- |
| Regions    | 1 day                   | Refresh on application startup                                    |
| Localities | 1 day                   | Daily background job                                              |
| Services   | 1 hour                  | Account-specific; refresh when a `service_id` is rejected         |
| Quotes     | Don't cache across days | Tariffs and `delivery_message` can change; quote at checkout time |

### Example: cache comunas in the browser

```js
// Cache localities in memory or localStorage
const CACHE_KEY = 'nextday_localities'
const CACHE_DURATION = 24 * 60 * 60 * 1000 // 24 hours

async function getLocalities() {
  const cached = localStorage.getItem(CACHE_KEY)

  if (cached) {
    const { data, timestamp } = JSON.parse(cached)
    if (Date.now() - timestamp < CACHE_DURATION) {
      return data
    }
  }

  const response = await fetch('https://api.4nortes.app/api/v1/localities', {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Accept': 'application/json',
    },
  })

  if (!response.ok) {
    throw new Error(`Failed to load localities: HTTP ${response.status}`)
  }

  const { data } = await response.json()

  localStorage.setItem(CACHE_KEY, JSON.stringify({
    data,
    timestamp: Date.now()
  }))

  return data
}
```
