Skip to content
DezfilDocsv0.1.0

API quickstart

Create an API token, then list your maps, upload a CSV, run SQL, add a layer and publish the map, with curl or JavaScript.

  • You need: a running Dezfil and an account on it. To run Dezfil yourself, follow Install first. On an instance someone else runs, ask its admin for an account.
  • Data: the 48 cities of Your first map, saved as world-cities.csv in the folder you run the examples from.
  • Tools: curl, or Node.js 24 or later. Save a JavaScript tab under the name in its title and run it with node, such as node list-maps.mjs.

The examples call http://localhost:3000; change the address if yours differs. Replace <YOUR_TOKEN> with your token, and a value such as <MAP_ID> with the one an answer before it gave. Answers are spread over lines, though curl prints each on one, and ... stands for fields left out.

  1. In Dezfil, select Settings at the bottom of the left rail, then API & MCP.
  2. In API / MCP tokens, enter a Token name, such as Quickstart, and select Create token.
  3. Select Copy token, then Done. Dezfil shows the token only once.

You should see the new token in the list, marked Never used.

Sign in with your email and password, then ask for a token with the session cookie that signing in gives you:

curl

Terminal
curl -s -o /dev/null -c cookies.txt http://localhost:3000/api/auth/sign-in \
-H "Content-Type: application/json" -H "Origin: http://localhost:3000" \
-d '{"email": "<YOUR_EMAIL>", "password": "<YOUR_PASSWORD>"}'
curl -s -b cookies.txt http://localhost:3000/api/account/tokens \
-H "Content-Type: application/json" -H "Origin: http://localhost:3000" \
-d '{"name": "Quickstart"}'
rm cookies.txt
Output
{
"id": "<TOKEN_ID>",
"name": "Quickstart",
"createdAt": "2026-10-20T09:00:00.791Z",
"lastUsedAt": null,
"revokedAt": null,
"expiresAt": null,
"secret": "<YOUR_TOKEN>"
}
  • secret is your token, the <YOUR_TOKEN> of the calls below. It begins with dz_token_, and no other answer shows it.
  • Origin names the instance’s own address. Dezfil refuses a sign-in, or a change sent with a session cookie, unless it does, so another website can’t act with your session.
  • expiresAt stays null unless an admin sets New tokens expire after in Settings › Security, as Security and rules describes.
  • -c cookies.txt keeps the session cookie of the sign-in, whose own answer -o /dev/null drops, and -b cookies.txt sends it back. rm cookies.txt deletes it.

To keep the token in your shell rather than copy it, run the token request through jq in place of the script’s second command, then check the variable:

Terminal
export DEZFIL_API_TOKEN="$(curl -s -b cookies.txt http://localhost:3000/api/account/tokens \
-H "Content-Type: application/json" -H "Origin: http://localhost:3000" \
-d '{"name": "Quickstart"}' | jq -r .secret)"
echo "${DEZFIL_API_TOKEN:0:9}"
Output
dz_token_
  • jq -r .secret keeps only the token, so the export line prints nothing.
  • echo prints the token’s first nine characters. null instead means the answer held an error, not a token: Troubleshooting lists the common ones.

Then write $DEZFIL_API_TOKEN where a curl call shows <YOUR_TOKEN>, as in -H "Authorization: Bearer $DEZFIL_API_TOKEN". In JavaScript, process.env.DEZFIL_API_TOKEN reads it, and Connect an AI agent uses the same variable.

curl

Terminal
curl -s http://localhost:3000/api/maps \
-H "Authorization: Bearer <YOUR_TOKEN>"
Output
[]
  • Authorization: Bearer carries the token. Every call below sends it, and acts as your account.
  • A new account has no maps, so the list is empty. If you made Your first map with this account, it lists that map. Later it holds every map you reach: your own, and those in projects shared with you.

curl

Terminal
curl -s http://localhost:3000/api/tables \
-H "Authorization: Bearer <YOUR_TOKEN>" \
Output
{
"id": "<TABLE_ID>",
"sourceFilename": "world-cities.csv",
"format": "csv",
...
"geometryColumn": "geometry",
"geometryType": "POINT",
...
"rowCount": 48,
...
"name": "world_cities",
...
}
  • file is the one form field an upload takes. To upload several files, send one request for each.
  • The dataset takes its name from the file: world-cities.csv becomes world_cities. If you already have one, such as Your first map’s, the new one is world_cities_2: write the name your answer gives in place of world_cities in the SQL of steps 4 and 5.
  • Dezfil reads the latitude and longitude columns as each city’s position, and stores the points in a new geometry column. CSV columns lists the names it recognizes.

curl

Terminal
curl -s http://localhost:3000/api/query \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT name, country, population FROM world_cities ORDER BY population DESC LIMIT 3"}'
Output
{
"columns": [
{ "name": "name", "type": "VARCHAR" },
{ "name": "country", "type": "VARCHAR" },
{ "name": "population", "type": "BIGINT" }
],
"rowCount": 3,
"preview": [
{ "name": "Tokyo", "country": "Japan", "population": "35676000" },
{ "name": "New York", "country": "United States of America", "population": "19040000" },
{ "name": "Mexico City", "country": "Mexico", "population": "19028000" }
],
"previewTruncated": false,
"runtimeMs": 4
}
  • sql holds one read-only SELECT over your datasets. Query with SQL shows what it can do.
  • rowCount counts every row of the result. preview holds the first rows, and previewTruncated is true when some were left out. Limits gives the preview’s size.
  • population arrives as text: BIGINT numbers travel as strings, so no digit is lost.
  • runtimeMs is how long the query took, so yours differs.

Create an empty map:

curl

Terminal
curl -s http://localhost:3000/api/maps \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"name": "World cities"}'
Output
{
"id": "<MAP_ID>",
"name": "World cities",
...
"published": false,
...
"editRevision": "<MAP_REVISION>",
...
}

The editRevision names this state of the map. Each change to the map sends the newest one in the X-Edit-Revision header, and answers with the next. Edit with revisions has the rules.

Add a layer of the cities with more than 10 million people, using the map’s id and editRevision:

curl

Terminal
curl -s http://localhost:3000/api/maps/<MAP_ID>/layers \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Content-Type: application/json" \
-H "X-Edit-Revision: <MAP_REVISION>" \
-d '{"name": "Big cities", "sql": "SELECT name, population, geometry FROM world_cities WHERE population > 10000000"}'
Output
{
"id": "<LAYER_ID>",
"mapId": "<MAP_ID>",
"name": "Big cities",
"sql": "SELECT name, population, geometry FROM world_cities WHERE population > 10000000",
"style": { ... },
...
"editRevision": "<LAYER_REVISION>",
...
}
  • sql decides what the layer draws: any query that returns a geometry column. This one keeps 15 of the 48 cities.
  • Without a style, the layer takes the instance’s default look. The operation reference lists every style field.
  • The answer’s editRevision is the map’s new one. The next change sends it.

In the app, the map appears on your Maps page. Open it to see the 15 cities. Your first map’s World cities, if you have it, shows all 48.

curl

Terminal
curl -s -X POST http://localhost:3000/api/maps/<MAP_ID>/publish \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "X-Edit-Revision: <LAYER_REVISION>"
Output
{
"token": "<PUBLISH_TOKEN>",
"publishedAt": "2026-10-20T09:00:01.521Z",
"editRevision": "<PUBLISH_REVISION>",
"lastChangedAt": "2026-10-20T09:00:01.521Z",
"lastChangedBy": { "name": "<YOUR_NAME>", "sqlName": "<YOUR_SQL_NAME>" }
}

Open http://localhost:3000/v/<PUBLISH_TOKEN>. You should see the map with its 15 cities. Anyone with the link can open it, without an account.

  • The published view opens in the dark theme. Send the body {"theme": "light"} with the request for the light one.
  • To stop sharing, send DELETE to the same address with the map’s newest editRevision. The link stops working at once.

Share and publish covers publishing in the app and embedding the map in a web page.

A call answers 401 authentication_required or token_expired

Section titled “A call answers 401 authentication_required or token_expired”

The call carried no token, or one Dezfil doesn’t accept. Check that the Authorization header holds the whole token, beginning dz_token_. A revoked token answers the same: create a new one. A token past its expiresAt answers token_expired instead, and needs a new one too.

From the curl script of step 1, this answer to the token request means the sign-in before it failed. Check the email, the password and the Origin header.

Signing in or creating a token answers 403 forbidden

Section titled “Signing in or creating a token answers 403 forbidden”

The answer says “This request must come from the same instance.” The Origin header is missing or names another address. Send the address the calls use, such as http://localhost:3000.

The answer says “Read this item first, then send its editRevision in X-Edit-Revision.” The change carried no X-Edit-Revision, or a malformed one. Send the editRevision from the map’s newest answer.

The map changed after the answer your revision came from, for example in the builder. Nothing was changed. Read the map with GET /api/maps/<MAP_ID>, see what changed, then make your change again with that answer’s editRevision, as Edit with revisions shows.

Creating a token or publishing answers 403 rule_refused

Section titled “Creating a token or publishing answers 403 rule_refused”

A rule an admin sets leaves your account out: only admins may do this or, for publishing, no one. The answer’s rule field names the rule. An admin changes it in Settings: Who may create tokens under Security, Who may publish under Publishing. Security and rules lists both.

  • How the API works: the address, signing in, edit revisions, who may do what, paging and errors.
  • Operation reference: every operation with its fields, built from the API description, openapi.json.
  • Errors: what an error answer holds, and what to do about each common code.
  • Limits: upload sizes, SQL results and the settings that change them.
  • Connect an AI agent: the same operations as tools for Claude Code, Codex or Cursor.
Dezfil · Apache-2.0docs from 7439201b