Zors AI - Franchise Territory Management Platform Logo
Zors
v1

Territory REST API

Read your organisation's franchise territories programmatically — names, status, type, centre points, the census geographies they are built from, and boundary polygons as GeoJSON. Authenticate with an API key you create in Zors; the key determines which organisation's data you see.

Quickstart

Three steps from nothing to a working request.

1

Create an API key

In the Zors app, open the account menu → Workspace Settings API Keys, under the Management heading. Give the key a name and create it. You must be an organisation admin.

The key is shown once. Copy it immediately — it cannot be retrieved later, only revoked and replaced.

2

Store it as an environment variable

Keys are long-lived secrets with full read access to your territories. Keep them server-side, never in client-side code or a public repository.

3

Make your first request

export ZORS_API_KEY="zors_live_your_key_here"

curl -H "Authorization: Bearer $ZORS_API_KEY" \
  "https://us-central1-zors-ai.cloudfunctions.net/public_api/v1/territories"

Authentication

Every request needs an API key. Two header forms are accepted — use whichever suits your client. If both are present, the Authorization header wins.

Authorization: Bearer zors_live_your_key_here

# or

X-API-Key: zors_live_your_key_here
About keys
  • Keys start with zors_live_ and are 42 characters long.
  • A key is tied to one organisation. That organisation is resolved from the key alone — there is no organisation parameter, and no key can read another tenant's data.
  • Keys are stored hashed. Zors cannot show you a key again after creation, and cannot recover a lost one.
  • There is no rotation operation. To rotate, create a new key, move your integration over, then revoke the old one.
  • Revoking takes effect immediately; anything still using that key starts receiving 401 responses.

Endpoints

All endpoints are relative to:

https://us-central1-zors-ai.cloudfunctions.net/public_api

List territories

Returns every territory belonging to the organisation the API key was issued for.

GET
/v1/territories
Request
curl -H "Authorization: Bearer $ZORS_API_KEY" \
  "https://us-central1-zors-ai.cloudfunctions.net/public_api/v1/territories"

Response
FieldTypeNullableDescription
territoriesobject[]never nullArray of territory summaries. See the fields below.
countnumbernever nullNumber of territories in this response.
Each item in territories
FieldTypeNullableDescription
idstringnever nullUnique territory identifier. Use this as the {id} path segment on the detail endpoint.
namestring
nullable
Territory name as entered in Zors.
statusstring
nullable
One of the status values listed below. Unrecognised values are returned unchanged rather than mapped, so treat this as an open set.
typestring
nullable
One of the type values listed below. Like status, unmapped values pass through unchanged.
centerPointnumber[]
nullable
Centroid as [longitude, latitude] — GeoJSON axis order, longitude first. Null if the territory has no stored centroid.
lastBoundaryUpdatestring
nullable
ISO-8601 timestamp of the last boundary edit. Null if the boundary has never been edited since the territory was created.
Example response
{
  "territories": [
    {
      "id": "aBc123XyZ",
      "name": "North Dallas",
      "status": "Available",
      "type": "Brick and Mortar",
      "centerPoint": [
        -96.8089,
        32.9483
      ],
      "lastBoundaryUpdate": "2026-07-14T16:22:41.000Z"
    }
  ],
  "count": 1
}

Retrieve a territory

Returns a single territory including its boundary polygon and the census geographies it is built from.

GET
/v1/territories/{id}
Path parameters
FieldTypeRequiredDescription
idstringYesThe territory id, as returned by the list endpoint.
Request
curl -H "Authorization: Bearer $ZORS_API_KEY" \
  "https://us-central1-zors-ai.cloudfunctions.net/public_api/v1/territories/aBc123XyZ"

Response
FieldTypeNullableDescription
territoryobjectnever nullThe territory object. See the fields below.
The territory object
FieldTypeNullableDescription
idstringnever nullUnique territory identifier. Use this as the {id} path segment on the detail endpoint.
namestring
nullable
Territory name as entered in Zors.
statusstring
nullable
One of the status values listed below. Unrecognised values are returned unchanged rather than mapped, so treat this as an open set.
typestring
nullable
One of the type values listed below. Like status, unmapped values pass through unchanged.
centerPointnumber[]
nullable
Centroid as [longitude, latitude] — GeoJSON axis order, longitude first. Null if the territory has no stored centroid.
lastBoundaryUpdatestring
nullable
ISO-8601 timestamp of the last boundary edit. Null if the boundary has never been edited since the territory was created.
descriptionstring
nullable
Free-text description entered in Zors.
centerPointGeoJSONobject
nullable
The centroid as a GeoJSON Point: {"type":"Point","coordinates":[lng,lat]}. Null whenever centerPoint is null.
boundaryobject
nullable
The territory outline as a GeoJSON Polygon or MultiPolygon. Null if the territory has no stored boundary yet.
tractsarraynever nullCensus tracts making up the territory. Always an array — empty when the territory was not built from tracts. Element shape varies; treat elements as opaque.
zipCodesarraynever nullZIP codes making up the territory. Always an array. Elements are objects for ZIP-built territories and plain strings for tract-built ones, so check the type before reading.
statestring
nullable
Two-letter state code, e.g. "TX".
stateFipsstring
nullable
Census state FIPS code.
countyFipsstring
nullable
Census county FIPS code.
Example response
{
  "territory": {
    "id": "aBc123XyZ",
    "name": "North Dallas",
    "status": "Available",
    "type": "Brick and Mortar",
    "centerPoint": [
      -96.8089,
      32.9483
    ],
    "lastBoundaryUpdate": "2026-07-14T16:22:41.000Z",
    "description": "Territory covering north Dallas suburbs.",
    "centerPointGeoJSON": {
      "type": "Point",
      "coordinates": [
        -96.8089,
        32.9483
      ]
    },
    "boundary": {
      "type": "Polygon",
      "coordinates": [
        [
          [
            -96.8712,
            32.9104
          ],
          [
            -96.7433,
            32.9104
          ],
          [
            -96.7433,
            32.9861
          ],
          [
            -96.8712,
            32.9861
          ],
          [
            -96.8712,
            32.9104
          ]
        ]
      ]
    },
    "tracts": [],
    "zipCodes": [
      "75080",
      "75081",
      "75082"
    ],
    "state": "TX",
    "stateFips": "48",
    "countyFips": "113"
  }
}

Status and type values

Territories carry a status and a type. Both are returned as human-readable strings. Treat them as an open set: a value the API does not recognise is passed through unchanged rather than replaced, so your code should handle unexpected strings without failing.

status
statusMeaning
AvailableOpen for sale, no commitment in place.
PendingA sale is in progress but not yet closed.
Sold (Not Open)Awarded to a franchisee; the unit is not trading yet.
Sold (Open)Awarded and operating.
For SaleAn existing unit being resold.
ProspectUnder consideration; not yet available.
ClosedNo longer operating.
type
typeMeaning
Brick and MortarFixed premises.
MobileService travels to the customer.
RemoteDelivered without a physical location.
OtherAnything not covered above.

Errors

Errors return a matching HTTP status and a JSON body with a single error key. There are no machine-readable error codes, so branch on the HTTP status rather than on the message text — wording may change.

{
  "error": "Invalid API key."
}
StatuserrorWhen it happens
401
Missing API key. Provide it as 'Authorization: Bearer <key>' or 'X-API-Key: <key>'.No API key was sent, or the Authorization header was not in the exact form "Bearer <key>".
401
Invalid API key.The key does not match any issued key.
401
This API key has been revoked.The key was valid but has since been revoked in Zors. Create a new key.
404
Not foundThe path is not one of the documented endpoints.
404
Territory not foundNo territory with that id exists in your organisation, it has been deleted, or it belongs to a different organisation.
405
Method not allowedOnly GET and OPTIONS are supported. Note this is checked before authentication, so a POST with an invalid key still returns 405.
429
Rate limit exceeded. Maximum 1000 requests per hour. Retry after N seconds.The key exceeded its hourly quota. The retry delay is in the message text; there is currently no Retry-After header.
500
Internal server errorAn unexpected error. Safe to retry with backoff; contact support if it persists.

Rate limits

Requests are limited to 1,000 per hour, per API key. The window is fixed rather than rolling: it starts on your first request and resets an hour later. Because the limit is per key, issuing a second key gives that integration its own separate quota.

Response headers
FieldTypeDescription
X-RateLimit-LimitnumberYour quota — currently 1000.
X-RateLimit-RemainingnumberRequests left in the current window. Approximate under concurrency.
X-RateLimit-ResetnumberUnix timestamp (seconds) when the window resets. Not sent on a 429 response.

Using it from a browser

The API sends permissive CORS headers, so browser requests will technically succeed from any origin. That is a convenience for local experimentation, not an invitation to call it from your front end — doing so exposes a long-lived key with read access to every territory you own. Call it from your server and pass the results on.

FieldTypeDescription
Access-Control-Allow-Origin*Any origin may call the API.
Access-Control-Allow-MethodsGET, OPTIONSOnly reads are supported.
Access-Control-Allow-HeadersContent-Type, Authorization, X-API-KeyHeaders you may send.
Access-Control-Expose-HeadersX-RateLimit-*Rate-limit headers are readable from browser JavaScript.

Stability

The API is versioned in the path. Everything documented here is v1. Additive changes — new fields on a response, new endpoints — can happen at any time, so parse responses tolerantly and ignore fields you do not recognise.

Two things are worth designing around today. The list endpoint returns every territory in one response with no pagination; if you have a large network, expect that response to be large, and expect pagination parameters to be added in future. And boundary polygons are simplified slightly for transport — they are suitable for mapping and analysis, but the authoritative description of a territory is the one in your franchise agreement, not this API.

Need something that isn't here?

Write access, filtering, pagination, webhooks and other resources are not part of v1. If your integration needs them, get in touch — and if you are automating workflows rather than building against an API, the Zapier integration may already do what you need.