On this page

Data Stores REST API

The Data Stores REST API lets an authenticated integration inspect, summarize, paginate, and tag entries stored by an Nviti form.

Use these endpoints to:

  • Read the form definition
  • Retrieve paginated form entries
  • Count entries that match a date range or tag filter
  • Calculate the most common values for selected fields
  • Add or remove tags from one returned page of records
  • Build external dashboards, exports, and batch processors

Endpoint

text
GET   https://api.nviti.ng/api/v1/data-stores/{form_hash}
GET   https://api.nviti.ng/api/v1/data-stores/{form_hash}/entries
GET   https://api.nviti.ng/api/v1/data-stores/{form_hash}/entries/count
GET   https://api.nviti.ng/api/v1/data-stores/{form_hash}/entries/unique-values
PATCH https://api.nviti.ng/api/v1/data-stores/{form_hash}/entries/tags

form_hash is the hash shown in the Data Stores dashboard. For example, the form dashboard URL:

text
https://nviti.ng/workspace/data-dashboards/OQxD4xbW

has the form hash OQxD4xbW, producing this API endpoint:

text
https://api.nviti.ng/api/v1/data-stores/OQxD4xbW

The old /api/v1/data-stores/query and /api/v1/data-stats/query routes are no longer available. The hash-based /api/v1/data-stores/{form_hash}/query route remains available for older integrations, but new integrations should use the focused REST endpoints above.

Authentication and Company Scope

The endpoint requires an Nviti API token using Bearer authentication:

http
Authorization: Bearer YOUR_API_TOKEN
Accept: application/json
Content-Type: application/json

Requests are always restricted to the authenticated user's tenant and selected company. A valid form hash from another company returns 404 Not Found.

When the API token can access more than one company, send the company's hash in the X-Company-ID header:

http
X-Company-ID: COMPANY_HASH

If X-Company-ID is omitted, Nviti uses the first company available to the authenticated user. Supplying it explicitly is recommended for production integrations.

Basic Requests

bash
curl --request GET \
  --url "https://api.nviti.ng/api/v1/data-stores/OQxD4xbW/entries/count?date_from=2026-06-04T00%3A00%3A00%2B01%3A00&date_to=2026-06-04T23%3A59%3A59%2B01%3A00" \
  --header "Authorization: Bearer ${NVITI_API_TOKEN}" \
  --header "X-Company-ID: ${NVITI_COMPANY_HASH}" \
  --header "Accept: application/json"
bash
curl --request GET \
  --url "https://api.nviti.ng/api/v1/data-stores/OQxD4xbW/entries?limit=20&page=1" \
  --header "Authorization: Bearer ${NVITI_API_TOKEN}" \
  --header "X-Company-ID: ${NVITI_COMPANY_HASH}" \
  --header "Accept: application/json"

Query Parameters

Field Type Required Default Description
date_from date/time No - Inclusive lower bound for the record's created_at timestamp.
date_to date/time No - Inclusive upper bound for created_at. Must be after or equal to date_from.
unique_fields string[] Required on /entries/unique-values - Fields to summarize.
tags string[] No [] Include records containing any supplied tag.
no_tags string[] No [] Exclude records containing any supplied tag.
limit integer No 100 Number of rows per page on /entries and /entries/tags. Minimum 1, maximum 1000.
page integer No 1 One-based page number on /entries and /entries/tags. Minimum 1.

Tag Mutation Body

PATCH /entries/tags accepts the same filters and pagination fields as /entries, plus:

Field Type Required Default Description
apply_tags string[] No [] Add tags to records returned on the requested page.
remove_tags string[] No [] Remove tags from records returned on the requested page.

Tag arrays accept a maximum of 50 tags per field. Each tag may contain up to 100 characters.

Form Selection

The form is selected exclusively by the {form_hash} URL segment. Do not send source or slug in the request body.

The hash-based URL prevents integrations from depending on a mutable form name or slug. The API resolves the hash inside the authenticated tenant and selected company before querying entries.

Returned row fields come from the form entry's mapped data. System fields id, tags, metadata, and created_at are always included.

Resources

Form Definition

GET /api/v1/data-stores/{form_hash} returns the definition of the selected form:

json
{
  "data": {
    "name": "Prayer Request",
    "description": "Collect prayer requests",
    "fields": [
      {
        "name": "phone_number",
        "label": "Phone",
        "type": "TextInput",
        "required": true
      }
    ]
  }
}

Entry Count

GET /entries/count returns the number of form entries matching the supplied date and tag filters.

json
{
  "data": {
    "count": 247
  }
}

count is not limited by page or limit.

Unique Values

GET /entries/unique-values calculates value frequencies for every field listed in unique_fields.

json
{
  "unique_fields": ["source", "status"]
}

Example response:

json
{
  "data": {
    "unique": {
      "source": {
        "distinct_count": 2,
        "values": [
          {
            "value": "whatsapp",
            "count": 180
          },
          {
            "value": "web",
            "count": 67
          }
        ]
      }
    }
  }
}

Important behavior:

  • unique_fields is required when requesting unique.
  • Dot notation may be used to read nested values, for example customer.country.
  • Missing fields are counted as a null value.
  • distinct_count reports the full number of distinct values.
  • The values list contains at most the 100 most frequent values.
  • The metric uses all matching records and is not limited by row pagination.

Entries

GET /entries returns a page of matching records, ordered by created_at from newest to oldest.

json
{
  "data": {
    "data": [
      {
        "phone_number": "2348000000000",
        "source": "whatsapp",
        "id": "665f04408b15f40f400c5192",
        "tags": ["processed"],
        "metadata": {
          "visitor_id": "visitor-123",
          "ip": "127.0.0.1"
        },
        "created_at": "2026-06-04T05:10:39+00:00"
      }
    ],
    "meta": {
      "page": 1,
      "limit": 20,
      "total": 247,
      "total_pages": 13,
      "has_more": true,
      "next_page": 2,
      "previous_page": null
    }
  }
}

Every row always includes:

Field Description
id MongoDB identifier for the stored record.
tags Current record tags. Returns an empty array when no tags exist.
metadata Entry metadata, such as visitor details captured alongside the entry. Returns an empty object when no metadata exists.
created_at Entry creation time formatted as an ISO 8601 timestamp.

Date Filtering

date_from and date_to filter records using created_at. Both boundaries are inclusive.

Use explicit ISO 8601 timestamps with an offset or Z suffix to avoid timezone ambiguity:

text
GET /api/v1/data-stores/OQxD4xbW/entries?date_from=2026-06-01T00:00:00+01:00&date_to=2026-06-07T23:59:59+01:00

Tag Filtering

Tags are top-level properties of stored form entries.

Match Any Tag with tags

tags uses ANY-match behavior. A record is included when it contains at least one requested tag.

json
{
  "tags": ["priority", "attendance"]
}

This matches a record tagged priority, a record tagged attendance, or a record containing both.

Exclude Any Tag with no_tags

no_tags excludes records containing any supplied tag.

json
{
  "no_tags": ["processed", "archived"]
}

This excludes records containing processed, archived, or both.

Combine Tag Filters

json
{
  "tags": ["attendance"],
  "no_tags": ["processed"]
}

This selects attendance records that have not yet been marked as processed.

Tag values are trimmed, empty values are discarded, and duplicate values are removed before querying or mutating records. Tag matching is case-sensitive.

Applying and Removing Tags

apply_tags and remove_tags modify only the records returned in rows.data for the requested page.

They do not modify:

  • Matching records on another page
  • Records excluded by filters
  • Records outside the selected company
  • Records outside the requested date range

Use PATCH /entries/tags whenever either mutation field is non-empty.

Apply Tags

json
{
  "no_tags": ["processed"],
  "apply_tags": ["processed"],
  "limit": 100,
  "page": 1
}

Tags are merged without creating duplicates.

Remove Tags

json
{
  "tags": ["processed"],
  "remove_tags": ["processed", "archived"],
  "limit": 100,
  "page": 1
}

Tags that are not currently present are ignored.

Apply and Remove in One Request

json
{
  "apply_tags": ["reviewed"],
  "remove_tags": ["processed"]
}

If the same tag appears in both apply_tags and remove_tags, removal wins.

Mutation Response

When tags are applied or removed, the response includes a tagging section:

json
{
  "data": {
    "rows": {
      "data": [],
      "meta": {}
    },
    "tagging": {
      "applied_tags": ["reviewed"],
      "removed_tags": ["processed"],
      "updated_count": 18
    }
  }
}

updated_count is the number of returned records whose stored tags actually changed. Records already in the requested final state are not counted as updated.

Filters, counts, and pagination metadata describe the matching records before tag mutations are applied. Returned rows are refreshed and show their tags after mutation.

To retrieve each unprocessed entry once:

  1. Filter using no_tags: ["processed"].
  2. Call PATCH /entries/tags with apply_tags: ["processed"].
  3. Handle the returned rows.
  4. Repeat using page: 1 until rows.data is empty.

Always requesting page 1 is important because applying processed removes those records from the next no_tags: ["processed"] result set.

json
{
  "no_tags": ["processed"],
  "apply_tags": ["processed"],
  "limit": 100,
  "page": 1
}

apply_tags and remove_tags are executed by Nviti before the response is returned. This pattern marks a row as processed when it is retrieved, not when your downstream work completes. Use it when successful retrieval is your completion boundary or when your consumer can safely retry already-tagged rows. If you require strict acknowledgement after downstream processing, maintain the returned row IDs in your integration and use an acknowledgement workflow designed for that requirement.

Complete REST Workflow

text
GET /api/v1/data-stores/OQxD4xbW
GET /api/v1/data-stores/OQxD4xbW/entries/count?date_from=2026-06-01T00:00:00+01:00&date_to=2026-06-30T23:59:59+01:00&tags[]=attendance&no_tags[]=archived
GET /api/v1/data-stores/OQxD4xbW/entries/unique-values?unique_fields[]=source&date_from=2026-06-01T00:00:00+01:00&date_to=2026-06-30T23:59:59+01:00
GET /api/v1/data-stores/OQxD4xbW/entries?limit=50&page=1&tags[]=attendance&no_tags[]=archived
PATCH /api/v1/data-stores/OQxD4xbW/entries/tags

Example tag mutation body:

json
{
  "tags": ["attendance"],
  "no_tags": ["archived"],
  "apply_tags": ["exported"],
  "remove_tags": ["pending-export"],
  "limit": 50,
  "page": 1
}

Counting Entries Only

For dashboards that only need a count, call the count endpoint:

bash
curl --request GET \
  --url "https://api.nviti.ng/api/v1/data-stores/OQxD4xbW/entries/count?date_from=2026-06-04T00%3A00%3A00%2B01%3A00&date_to=2026-06-04T23%3A59%3A59%2B01%3A00" \
  --header "Authorization: Bearer ${NVITI_API_TOKEN}" \
  --header "X-Company-ID: ${NVITI_COMPANY_HASH}" \
  --header "Accept: application/json"

This avoids calculating definitions, unique values, or rows that the caller does not need.

Validation and Error Responses

401 Unauthorized

The Bearer token is missing, invalid, or revoked.

403 Forbidden

The authenticated user does not have a usable tenant or selected company context.

404 Not Found

The supplied form hash is invalid or does not belong to the selected tenant and company.

422 Unprocessable Entity

The request failed validation. The response identifies invalid fields:

json
{
  "message": "The unique fields field is required.",
  "errors": {
    "unique_fields": [
      "The unique fields field is required."
    ]
  }
}

Common validation failures:

  • Requesting /entries/unique-values without unique_fields
  • Setting limit above 1000
  • Setting date_to before date_from
  • Supplying more than 50 tags in a tag field

Performance Recommendations

  • Use /entries/count for simple counters.
  • Avoid requesting unique across very large unbounded datasets; use date and tag filters.
  • Keep row pages reasonably sized. The maximum is 1000, but smaller batches reduce processing time and retry cost.
  • Use explicit date ranges for scheduled reports.
  • Use tags to create resumable processing workflows.
  • Store API tokens in environment variables or a secrets manager. Never place them in source control.
  • Send X-Company-ID explicitly when an API token can access multiple companies.

Keywords

data stores API, data query API, form hash, form entries API, entry tags, batch processing, data pagination, unique values, API integration, data exports