Skip to content

sbroenne/dvq

Repository files navigation

dvq

dvq is a small CLI and Node.js library for querying Dataverse OData endpoints with Azure CLI credentials.

What exists today

  • CLI for running OData paths against /api/data/v9.2/
  • Azure authentication via az login
  • Pretty-printed JSON output
  • Library helpers for URL building, token acquisition, single requests, and paginated requests

Install

npm install @sbroenne/dvq

Run without installing:

npx @sbroenne/dvq --help

Requirements

  • Node.js 20 or later
  • Azure CLI (az)
  • Access to a Dataverse environment

Authenticate once before using the CLI or library:

az login

For development and release validation, npm test runs the unit suite and npm run test:integration runs the live Dataverse checks when .env.integration is configured.

Configuration

Set the base environment URL:

export DATAVERSE_URL="https://yourorg.crm.dynamics.com"

You can also pass --url on the CLI instead of setting DATAVERSE_URL.

By default, dvq requests Dataverse formatted-value annotations so OptionSet labels and other display values are present in responses. Use --no-formatted-values to disable that default, or --header to add custom request headers when debugging environment-specific behavior.

Use --verbose to emit request tracing to stderr. This is useful when you need to see the exact request URL, pagination flow, and sanitized headers without mixing trace output into the JSON response on stdout.

CLI quick start

The query is an OData path relative to /api/data/v9.2/.

dvq "accounts?$top=5"

With an explicit URL:

dvq --url "https://yourorg.crm.dynamics.com" "accounts?$select=name&$top=5"

From a file:

dvq --file query.odata

From stdin:

echo "accounts?$top=5" | dvq --url "https://yourorg.crm.dynamics.com"

Verify auth and connectivity:

dvq --whoami

Disable formatted-value annotations for a leaner payload or to isolate header-related issues:

dvq --url "https://yourorg.crm.dynamics.com" --no-formatted-values "accounts?$top=5"

Add custom request headers:

dvq --url "https://yourorg.crm.dynamics.com" -H "ConsistencyLevel: eventual" "accounts?$top=5"

Trace auth, request, and pagination flow to stderr:

dvq --verbose --url "https://yourorg.crm.dynamics.com" --all "accounts?$select=name&$top=5"

Follow @odata.nextLink pages automatically:

dvq --all "accounts?$select=name"

CLI usage

dvq [options] [query]
Option Argument Description
[query] OData path Inline query path after /api/data/v9.2/
-f, --file <path> Read the OData path from a file
-a, --all Follow @odata.nextLink pages up to the built-in safety cap
-u, --url <url> Use this Dataverse base URL instead of DATAVERSE_URL
-v, --verbose Print auth/request/pagination tracing to stderr
--no-formatted-values Do not send the default Prefer header for formatted values
-H, --header <name:value> Add a request header; may be repeated
--whoami Call WhoAmI and print the response
--version Print the package version
--help Show help text

Notes:

  • If neither [query] nor --file is given, dvq reads stdin when input is piped.
  • Without --all, the CLI prints the first JSON response object exactly as returned by Dataverse.
  • With --all, the CLI prints a JSON array aggregated across pages.
  • By default, dvq sends Prefer: odata.include-annotations="OData.Community.Display.V1.FormattedValue".
  • --verbose writes tracing to stderr and never prints the bearer token.

Library API

The package currently exports low-level helpers from src/lib.ts, not a single high-level query() function.

Common request flow

import { buildUrl, fetchOData, getToken } from '@sbroenne/dvq';

const baseUrl = 'https://yourorg.crm.dynamics.com';
const token = await getToken({ baseUrl });
const url = buildUrl('accounts?$top=5', baseUrl);
const data = await fetchOData(url, token);

console.log(data);

Fetch all pages

import { getToken, queryAll } from '@sbroenne/dvq';

const baseUrl = 'https://yourorg.crm.dynamics.com';
const token = await getToken({ baseUrl });
const rows = await queryAll('accounts?$select=name', token, baseUrl);

console.log(rows);

Main exports

Export Purpose
resolveDataverseUrl, getDataverseUrl Resolve the Dataverse base URL from an explicit value or DATAVERSE_URL
getDataverseScope Build the /.default scope for Azure auth
buildUrl Build a full Dataverse Web API URL from an OData path
buildHeaders Create request headers for Dataverse JSON calls, with optional annotation/header overrides
getToken Acquire an access token using AzureCliCredential
fetchOData Execute one HTTP request and parse the JSON response, with optional request overrides
queryAll Follow paginated @odata.nextLink responses and return one array, with optional request overrides
readQueryFile, readStdin CLI-oriented input helpers
ODataError Error type for non-2xx HTTP responses
API_PATH, DEFAULT_API_PATH, MAX_PAGES Public constants used by the helpers

Errors and behavior

Missing configuration:

DATAVERSE_URL environment variable is required.

Authentication failure:

Failed to get token. Run:
  az login
Target: https://yourorg.crm.dynamics.com

queryAll() stops after MAX_PAGES pages and throws if the safety cap is exceeded.

Dataverse request failures now include the request URL and, when possible, a targeted troubleshooting hint. Example for the header-related 400 seen on some entity reads:

HTTP 400: Both header name and value should be specified.
URL: https://yourorg.crm.dynamics.com/api/data/v9.2/msp_accountteams?$top=5
Hint: Dataverse rejected a request header. Retry with formatted values disabled to isolate the default Prefer header, and review any custom headers.

Contributing

See CONTRIBUTING.md.

Security

Please do not report suspected vulnerabilities in public issues. See SECURITY.md for the current reporting path and maintainer security checklist.

License

MIT © 2026 sbroenne

About

CLI for querying Dataverse OData endpoints with Azure CLI credentials

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors