dvq is a small CLI and Node.js library for querying Dataverse OData endpoints with Azure CLI credentials.
- 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
npm install @sbroenne/dvqRun without installing:
npx @sbroenne/dvq --help- Node.js 20 or later
- Azure CLI (
az) - Access to a Dataverse environment
Authenticate once before using the CLI or library:
az loginFor 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.
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.
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.odataFrom stdin:
echo "accounts?$top=5" | dvq --url "https://yourorg.crm.dynamics.com"Verify auth and connectivity:
dvq --whoamiDisable 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"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--fileis given,dvqreads 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,
dvqsendsPrefer: odata.include-annotations="OData.Community.Display.V1.FormattedValue". --verbosewrites tracing to stderr and never prints the bearer token.
The package currently exports low-level helpers from src/lib.ts, not a single high-level query() function.
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);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);| 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 |
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.
See CONTRIBUTING.md.
Please do not report suspected vulnerabilities in public issues. See SECURITY.md for the current reporting path and maintainer security checklist.
MIT © 2026 sbroenne