npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nytka/plugin-bing

v0.1.0

Published

Bing Webmaster Tools connector for nytka projects. Collects traffic, queries, pages, inbound links and crawl health into datasets/ and registers them with provenance.

Readme

@nytka/plugin-bing

Bing Webmaster Tools connector for nytka projects. Collects traffic, top queries, top pages, inbound link counts and crawl health for a verified site into the project's datasets/ and registers each in datasets/index.json with provenance.

Read-only, by construction. The same API carries SubmitUrl, SubmitUrlBatch, SubmitContent, SubmitFeed, AddBlockedUrl, SaveCrawlSettings, AddSiteRoles and RemoveSite — some of them destructive against a client's property. This package cannot reach any of them: the client issues only GET, and only to the six methods in a frozen allowlist. A method outside the list is refused before a URL is built. Writing to Bing is a separate decision under 0008, and this connector is not where it gets made.

Why a second search engine

Every other search-side connector reports on Google. Bing Webmaster Tools is the same class of first-party data for the other engine, free with a verified account and a single API key — no OAuth, no service account, no Cloud project. Two things it gives that nothing else in the line does:

  • Inbound links, free. links returns the pages of your site Bing's index has links to, with counts. One engine, your own sites only, no competitor coverage — it is not a backlink tool — but it is link data nobody is billing you for.
  • A first-party signal for what AI assistants see. Bing's index is what Copilot reads, and its traffic figures count the Chat vertical alongside web results (see the comparison caveat below).

Install

npm install @nytka/plugin-bing

One dependency, @nytka/core, which has none. The API is JSON over HTTP and Node's built-in fetch speaks it; there is no client library to carry.

Setup

About five minutes, once per user. There is no key file and nothing to put in private/.

1. Verify the site

https://www.bing.com/webmasters → sign in (any Microsoft, Google or Facebook account) → add the site and verify it, if not already done. A site that is not verified returns nothing.

If the site is already in Google Search Console, Import from there is the fastest route: Bing reads the verified properties from your Google account and verifies them in one step.

2. Generate the API key

Settings (top right) → API access → accept the terms on first use → Generate API Key.

The key belongs to the user, not to a site: one key reads every site verified under that account. Bing issues one key per user, and generating a new one deletes the old one, so rotating it means updating .env in every project that carries it.

3. Configure

One .env at the project root. Every key for the project lives there and nowhere else.

The names below ship with the package too, so recovering them later never means re-reading this guide:

cat node_modules/@nytka/plugin-bing/.env.example >> .env
BING_WEBMASTER_API_KEY=<the key>
BING_SITE=https://example.com/

BING_WEBMASTER_API_KEY is the key itself — the value, not a path. Named for Webmaster Tools specifically because Microsoft issues other Bing keys (the Search APIs) that this connector cannot use.

4. Verify, and get the site string right

npx nytka-bing sites

Listing your site as [verified] means every step above worked.

Copy BING_SITE from this output — do not compose it by hand. Bing keys a site by its full URL, scheme and trailing slash included, and a spelling that differs from its own is refused rather than matched. https://example.com/ and http://example.com are two sites to Bing. sites prints the exact string.

Use

npx nytka-bing sites                 # auth smoke test — lists readable sites
npx nytka-bing monthly               # monthly clicks/impressions, same columns as nytka-gsc monthly
npx nytka-bing traffic               # daily impressions and clicks, every day Bing retains
npx nytka-bing queries               # top queries: clicks, impressions, average positions
npx nytka-bing pages                 # top pages, same columns
npx nytka-bing links                 # pages with inbound links in Bing's index, with counts
npx nytka-bing crawl                 # crawl and index health, day by day
npx nytka-bing monthly --no-register # write the payload, leave the registry alone

None of these takes a date range, because the API has none. Each method returns whatever window Bing retains — the reference says six months for crawl statistics and does not say for the others — and the connector stores exactly that. There is no --from/--to on purpose: a client-side filter on a payload the agent never reads belongs in the script that reads it.

The surface, and the question each part answers

| Command | API method | Question | Refreshed by Bing | |---|---|---|---| | sites | GetUserSites | Which sites can this key read, and are they verified? | — | | monthly | GetRankAndTrafficStats | How much Bing traffic did the site get, month by month? | daily | | traffic | GetRankAndTrafficStats | …and day by day? | daily | | queries | GetQueryStats | Which queries put the site in front of Bing users, and did they click? | weekly | | pages | GetPageStats | Which pages does Bing show, and how do they perform? | weekly | | links | GetLinkCounts | Which pages of the site does Bing's index have links to, and how many? | — (snapshot) | | crawl | GetCrawlStats | Is Bing crawling and indexing cleanly — pages crawled, in index, response codes, robots blocks, malware flags? | daily |

monthly and traffic are one request each — the same one — bucketed and not bucketed.

Not collected, and why. GetKeyword / GetKeywordStats / GetRelatedKeywords are keyword research over any term, not data about your site; that is a different question and a different dataset shape, left for a later release. GetUrlLinks (who links to a given page) is the per-URL drill-down of links; the page-level counts answer the site-level question first. GetCrawlIssues, GetUrlInfo, GetUrlTrafficInfo, GetChildrenUrlInfo, GetFeeds are per-URL and per-feed detail behind crawl. All are reads, and any of them can be added to the allowlist when a project asks the question.

Several sites

A project may span domains. Comma-separate them, and each becomes its own dataset:

BING_SITE=https://example.com/, https://other.example/

One key reads all of them, provided each is verified under the same account. The site is part of every dataset id, so two sites never collide.

Programmatic:

import { run, collect, listSites } from '@nytka/plugin-bing'

const { id, rowCount, rawPath } = await run({ kind: 'monthly' })

run() returns counts and paths, never rows.

Bing and gsc side by side: not like-for-like

monthly writes the columns @nytka/plugin-gsc's monthly writes — Month, Clicks, Impr. — and its id, bing-monthly-example-com-2026-03-to-2026-09, sorts beside gsc-monthly-example-com-… in the registry. That is so the two series can be put on one chart on one column. It is not so they can be summed or compared as equals:

  • Bing counts every vertical. From 2023-03-24 on, GetRankAndTrafficStats includes impressions and clicks from Web, Chat, News, Images, Videos and Knowledge Panel. gsc's monthly is type: web only. A Bing click may be a Copilot citation.
  • Different windows. The Search Console API retains 16 months; Bing returns what it holds, and the reference only commits to six months for crawl statistics.
  • Different lag. gsc ends its range three days back because Google finalises late. Bing says its traffic data updates daily and its query and page data weekly; this connector stores every row Bing returns and does not trim. Expect the last day or two to move on re-collection.

The registry summary of every monthly entry says "All verticals, not web only" for this reason.

Troubleshooting

| What you see | What it means | |---|---| | BING_WEBMASTER_API_KEY is not set … and exit 0 | The project has not configured Bing — step 3. Not an error: capability is configuration presence | | Bing rejected the API key (InvalidApiKey) | The key is wrong or was regenerated in the portal since it was copied — step 2 | | Bing refused GetRankAndTrafficStats (HTTP 400): … | Almost always the site string. Compare BING_SITE against sites output character for character — step 4. Otherwise the site is not verified under the account that owns the key | | sites lists the site as NOT verified | Step 1 was not finished. Nothing collects until it is | | no project.yaml found walking up from cwd | Not inside a nytka project. cd to the project root | | unexpected date from Bing: … | Bing changed its date serialisation. The collection fails rather than bucketing rows under a guess — file it | | Runs fine, monthly returns 0 rows | Bing has no traffic data for the site yet. A newly verified site takes days to fill | | Bing rate-limited … (HTTP 429) | Retry later. Microsoft publishes no limit for the read methods, so none is claimed here |

What it writes

| Path | Committed? | |---|---| | datasets/payloads/<id>.json | no — the directory ships its own .gitignore | | datasets/index.json | yes — one entry, added or replaced |

The dataset id

monthly   bing-monthly-example-com-2026-03-to-2026-09
traffic   bing-traffic-example-com-2026-03-to-2026-09
queries   bing-queries-example-com-2026-03-to-2026-09       the months the rows span
pages     bing-pages-example-com-2026-03-to-2026-09
crawl     bing-crawl-example-com-2026-03-to-2026-09

links     bing-links-example-com-2026-09                    the month the snapshot was taken

Every id names a period at month granularity, and dateRange keeps the exact span the rows covered. Bing's methods take no parameters beyond the site, so every collection is the recurring kind: the same site collected on two days of one month is one series. The id must not move between them — otherwise a scheduled daily run leaves thirty near-identical entries for one series, which is the failure @nytka/plugin-gsc 0.3.0 fixed and this package inherits the fix for. Re-collecting within a month replaces the entry and its payload; a new month at either end of Bing's window is a new entry.

This is the opposite of gsc's query, which keys on the exact window, and the difference is the same rule applied to a different case: there the user chooses a window and dimensions, so two runs can be two different questions and a shared id would destroy one. Here there is nothing to choose.

links has no dates — a count is Bing's index as of now — so its entry carries dateRange: null and period: "2026-09", and re-taking the snapshot within a month replaces it. An empty series (a site with no data yet) is keyed to the month it was collected in: "nothing here as of September" is one fact, and October's nothing is another.

Every date it writes — collectedAt, both ends of dateRange, the months inside the id — is a calendar date. The rows' dates are the days Bing meant: the API serialises each as /Date(1316156400000-0700)/, an instant plus the offset Bing reported it in, and the connector reads the day in that offset rather than in UTC or in the local zone. A value that is not that shape fails the collection instead of being bucketed under a guess.

The registry writer preserves the file's existing formatting: adding one dataset produces a one-entry diff.

Rules it follows

  • Payloads never enter agent context. Query them with a script; write conclusions to research/. The CLI prints row counts and paths for this reason.
  • The key never appears in output. It travels as a query parameter, which is how the API takes it; no URL is printed, and any response body is redacted before it can reach an error message.
  • sites prints URLs and verification status only. GetUserSites also returns each site's AuthenticationCode and DnsVerificationCode; they are dropped, because the smoke test prints to a terminal an agent reads.
  • The project is found by walking up for project.yaml, so it works at any install depth.
  • No YAML parsing, no config file. Secrets from .env, everything else from flags.

Limits worth knowing

  • What has and has not been run against the live API. Confirmed on 2026-09-19 with a made-up key: the JSON URL format, the query-parameter auth and the 400 fault envelope ({"ErrorCode":3,"Message":"ERROR!!! InvalidApiKey"} — the ERROR!!! prefix is not in Microsoft's sample, which is why the match is on the substring). Not yet confirmed: a successful response from any method. The row shapes are Microsoft's documented JSON samples, read the same day. The first run with a real key is the check.
  • SOAP and POX are gone. Microsoft retired both on 2026-08-31; the POX URL answers 404 today. JSON/HTTP is what this package uses and what remains, under the same key.
  • queries and pages are "top" lists. Bing decides how many; the connector stores what it is given. A page or query below Bing's cut is not in the payload and not in the totals.
  • Data is Bing's index, not the web. Link counts and crawl figures describe what Bingbot has seen, which is a subset of what exists.

Tests

npm test

node --test against recorded responses. No credential, no network, no client data. Sixty-one tests, including: the reference's own /Date(…)/ sample decoding to the day its XML twin spells, the DST change moving the offset and not the day, month bucketing losing and inventing nothing, the id holding still across a month of daily runs, every write method refused before fetch is reached, and the key absent from every error the package can produce.