First ten minutes.

From a new key to a working sync, in seven calls. Every answer below is what the live API sent back when the call was run.

Working — v1 at api.ardaform.net

1 · A key

In the portal, open the project, then API keys → New key. Tick what the key may do, area by area; for a first session, everything on Datasets and Records is enough. The whole key is shown once — copy it then.

shell your computer
# every example on these pages reads the key from here
export ARDA_KEY=af_…your key…

2 · Confirm it

The answer names the key, what it may do (scopes), its project, and the account's limits. Read limits once when your program starts and you will never discover a ceiling by hitting it.

curl "https://api.ardaform.net/v1/auth" \
  -H "Authorization: Bearer $ARDA_KEY"
Answer · 200 OKran live 2026-10-27 · test project
{
  "key": {
    "name": "api npm test",
    "prefix": "af_••••••••",
    "scope": "write",
    "branch": null,
    "scopes": {
      "code": {
        "r": "*",
        "w": "*",
        "a": "*",
        "d": "*"
      },
      "datasets": {
        "r": "*",
        "w": "*",
        "a": "*",
        "d": "*"
      },
      "records": {
        "r": "*",
        "w": "*",
        "a": "*",
        "d": "*"
      },
      "files": {
        "r": "*",
        "w": "*",
        "a": "*",
        "d": "*"
      }
    },
    "expires_on": null,
    "created_at": "2026-08-21T08:49:42.652Z"
  },
  "project": {
    "slug": "api-test",
    "name": "API test",
    "status": "active"
  },
  "space": null,
  "account": {
    "name": "ArdaForm Test"
  },
  "limits": {
    "max_projects": 3,
    "max_devices": 10,
    "max_users": 1,
    "max_invited": 0,
    "max_datasets": 20,
    "max_spaces": 3,
    "storage_mb": 50,
    "console": false,
    "marketplace": true,
    "marketplace_fee": 20,
    "gui": true,
    "detect_priority": "normal",
    "support": "email",
    "webhooks": true,
    "webhook_endpoints": 2,
    "webhook_per_min": 60,
    "api_access": true,
    "api_keys": 2,
    "api_per_min": 600,
    "api_burst": 1200,
    "max_records_per_call": 10000,
    "max_bytes_per_call": 10485760
  }
}

A key that has no webhooks, project or settings entry — like this one, made in August — is refused on those areas until it is edited in the portal. Why.

3 · A dataset

Usually a dataset comes from your app's manifest.json when a commit is packed. Data that only your own systems use can have one made here. This one is split by a field called branch, so a device reads only its branch's records.

curl -X POST "https://api.ardaform.net/v1/datasets" \
  -H "Authorization: Bearer $ARDA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Docs products","key":"zz-docs-products","description":"Prices, per branch","partition_field":"branch"}'
Answer · 201 Createdran live 2026-10-27 · test project
{
  "dataset": {
    "key": "zz-docs-products",
    "name": "Docs products",
    "description": "Prices, per branch",
    "access": "rw",
    "partition_field": "branch",
    "retention_mode": "never",
    "retention_value": null,
    "storage_limit_bytes": null,
    "cursor": 0,
    "records": 0,
    "bytes": 0,
    "updated_at": null,
    "created_at": "2026-10-27T20:10:00.663Z"
  }
}

4 · Write records

A record is any JSON object. Give it an id and sending it again updates it. PUT replaces the whole dataset with what you send — the right call for a catalogue published from somewhere else. A record may sit in several branches at once: its branch is a list.

curl -X PUT "https://api.ardaform.net/v1/datasets/zz-docs-products/records" \
  -H "Authorization: Bearer $ARDA_KEY" \
  -H "Content-Type: application/json" \
  -d '[{"id":"A1","name":"Sourdough","price":3.2,"branch":["oldbury","moseley"]},{"id":"A2","name":"Rye","price":2.9,"branch":"oldbury"}]'
Answer · 200 OKran live 2026-10-27 · test project
{
  "dataset": "zz-docs-products",
  "replaced": 2,
  "pruned": 0,
  "cursor": 3,
  "refused": 0,
  "exceptions": []
}

5 · Read them

Filter by the partition field's own name — ?branch=oldbury. Sourdough is in Oldbury and Moseley, so it is here; has_more says there is another page.

curl "https://api.ardaform.net/v1/datasets/zz-docs-products/records?branch=oldbury&limit=2" \
  -H "Authorization: Bearer $ARDA_KEY"
Answer · 200 OKran live 2026-10-27 · test project
{
  "dataset": "zz-docs-products",
  "total": 2,
  "limit": 2,
  "offset": 0,
  "has_more": false,
  "records": [
    {
      "key": "A1",
      "partitions": [
        "moseley",
        "oldbury"
      ],
      "created_at": "2026-10-27T20:10:01.364Z",
      "updated_at": "2026-10-27T20:10:01.364Z",
      "data": {
        "id": "A1",
        "name": "Sourdough",
        "price": 3.2,
        "branch": [
          "oldbury",
          "moseley"
        ]
      }
    },
    {
      "key": "A2",
      "partitions": [
        "oldbury"
      ],
      "created_at": "2026-10-27T20:10:01.364Z",
      "updated_at": "2026-10-27T20:10:01.462Z",
      "data": {
        "id": "A2",
        "name": "Rye",
        "price": 3.05,
        "branch": "oldbury"
      }
    }
  ]
}

6 · Sync

To keep a copy in step, ask for what changed since a cursor. since=0 is everything; store the cursor you get and send it next time.

curl "https://api.ardaform.net/v1/datasets/zz-docs-products/changes?since=0" \
  -H "Authorization: Bearer $ARDA_KEY"
Answer · 200 OKran live 2026-10-27 · test project
{
  "dataset": "zz-docs-products",
  "since": 0,
  "cursor": 6,
  "has_more": false,
  "purged": true,
  "changed": [
    {
      "key": "A1",
      "partitions": [
        "moseley",
        "oldbury"
      ],
      "created_at": "2026-10-27T20:10:01.364Z",
      "updated_at": "2026-10-27T20:10:01.364Z",
      "data": {
        "id": "A1",
        "name": "Sourdough",
        "price": 3.2,
        "branch": [
          "oldbury",
          "moseley"
        ]
      }
    },
    {
      "key": "A2",
      "partitions": [
        "oldbury"
      ],
      "created_at": "2026-10-27T20:10:01.364Z",
      "updated_at": "2026-10-27T20:10:01.462Z",
      "data": {
        "id": "A2",
        "name": "Rye",
        "price": 3.05,
        "branch": "oldbury"
      }
    },
    {
      "key": "A3",
      "partitions": [
        "moseley"
      ],
      "created_at": "2026-10-27T20:10:01.462Z",
      "updated_at": "2026-10-27T20:10:01.462Z",
      "data": {
        "id": "A3",
        "name": "Bagel",
        "price": 1.2,
        "branch": "moseley"
      }
    },
    {
      "key": "A4",
      "partitions": [
        "moseley"
      ],
      "created_at": "2026-10-27T20:10:01.541Z",
      "updated_at": "2026-10-27T20:10:01.541Z",
      "data": {
        "id": "A4",
        "name": "Scone",
        "price": 1.5,
        "branch": "moseley"
      }
    }
  ],
  "deleted": []
}

After one record was deleted and another updated, the next call with that cursor answered just those two — the update in changed, the deletion in deleted:

curl "https://api.ardaform.net/v1/datasets/zz-docs-products/changes?since=6" \
  -H "Authorization: Bearer $ARDA_KEY"
Answer · 200 OKran live 2026-10-27 · test project
{
  "dataset": "zz-docs-products",
  "since": 6,
  "cursor": 8,
  "has_more": false,
  "purged": false,
  "changed": [
    {
      "key": "A1",
      "partitions": [
        "moseley",
        "oldbury"
      ],
      "created_at": "2026-10-27T20:10:01.364Z",
      "updated_at": "2026-10-27T20:10:03.501Z",
      "data": {
        "id": "A1",
        "name": "Sourdough",
        "price": 3.4,
        "branch": [
          "oldbury",
          "moseley"
        ]
      }
    }
  ],
  "deleted": [
    "A3"
  ]
}

7 · Clean up

Deleting a dataset takes every record with it, and says how many.

curl -X DELETE "https://api.ardaform.net/v1/datasets/zz-docs-products" \
  -H "Authorization: Bearer $ARDA_KEY"
Answer · 200 OKran live 2026-10-27 · test project
{
  "deleted": {
    "dataset": "zz-docs-products",
    "records": 0
  }
}

Next