# Ni Biashara auth.md

How AI agents get access to the Ni Biashara diaspora business directory (nibiashara.com). Reading the directory needs no credentials. An agent that wants its own identity can register itself (below): no captcha, no email. The only OAuth tokens this site issues are those agent tokens, scope `directory.read`.

## Audience

AI agents and the people who run them: assistants, search agents and verification tools that look up African and Caribbean diaspora businesses in the US, Canada and beyond.

## No credentials needed to read

These are public and need no key, token or account:

- `GET https://nibiashara.com/api/v1/businesses.json`: every published listing
- `GET https://nibiashara.com/api/v1/businesses/<id>.json`: one listing by its id
- `GET https://nibiashara.com/api/v1/search?q=&city=&category=&metro=&limit=`: keyword search
- `GET https://nibiashara.com/api/v1/stats`: public counts (listings, claims this week, AI reads)
- `GET https://nibiashara.com/api/v1/reads` and `GET https://nibiashara.com/api/v1/report`: counts of AI crawlers and assistants reading the directory; `reads?business=<id>` for one listing
- `GET https://nibiashara.com/api/v1/cf-traffic`: live traffic to nibiashara.com counted by Cloudflare (last 24 hours, last hour, last 7 days, AI agent families), the same figures as its dashboard, refreshed every five minutes
- `POST https://nibiashara.com/a2a`: A2A JSON-RPC `message/send` (card: `/.well-known/agent-card.json`)
- `POST https://nibiashara.com/mcp`: MCP (Streamable HTTP, stateless), read-only tools `search_directory`, `get_business` and `directory_facets` (card: `/.well-known/mcp/server-card.json`)
- `GET https://nibiashara.com/api/v1/verify?name=|phone=|website=`: is a business listed (free, fair limit 60 checks a day per visitor). Each match has `verified`: true when the owner confirmed the listing or Ni Biashara has dealt with the business directly, false on a possible match; `ownership_verified` is the older name for it

Machine-readable descriptions: `/api/v1/openapi.json` (every route here, the limit headers, the error shapes, both sign-up routes and the paid route), `/.well-known/api-catalog`, `/llms.txt`.

## Limits you can read

Every answer from `GET https://nibiashara.com/api/v1/verify` names the limit you are held to and what is left, in the IETF RateLimit header fields:

- `RateLimit-Policy` lists each limit as `"name";q=<quota>;w=<window in seconds>`. The names are `network` (the free daily limit per network), `key` (an API key's monthly quota), `agent` (a registered agent's quota) and `burst` (a short-term limit at the edge).
- `RateLimit` says what is left as `"name";r=<remaining>;t=<seconds until it resets>`. There is no count for `burst`. If a count is missing, the limit in `RateLimit-Policy` still holds.
- A 429 also carries `Retry-After`, the seconds to wait, and a body that links the next step (see Errors).
- `burst` is enforced at the edge, in front of this API: 30 requests in 10 seconds per IP address, counted together across the routes it guards, among them `/api/v1/verify`, `/api/v1/search`, `/api`, `/mcp`, `/a2a` and the agent registration and token routes. Go over it and the edge blocks that address on those routes for 600 seconds. That 429 is the edge's own answer (Cloudflare error 1015), not ours: it has no `RateLimit` fields and none of the links described under Errors. By default it is an HTML page; ask with `Accept: application/json` to get it as JSON. Its `Retry-After` may say less than the block has left, so stop and wait the full 600 seconds. The listing JSON (`/api/v1/businesses.json` and one listing by its id), `/api/v1/stats` and `/api/v1/reads` are not behind this limit and stay open meanwhile.

`GET https://nibiashara.com/api/v1/search` has no daily limit and needs no key. It sends `RateLimit-Policy` with `burst` only.

## Get a higher limit

Reading is free and needs nothing. A higher limit changes one thing: `GET https://nibiashara.com/api/v1/verify` goes from 60 checks a day per network to 3,000 checks a month. There are two ways, in order of preference. Each needs a person once, because each is tied to an email address that person confirms.

### 1. An emailed API key (preferred)

Use this when the person who runs the agent can open a web page. It is one form, one emailed code and one header.

1. The person opens https://nibiashara.com/agents and signs the agent up. The page shows the key once.
2. We email a code to the address they gave. They enter it on the same page within 24 hours. Until then the key has 0 checks a month, and a check answers 429 with `type` ending `#key-not-confirmed`.
3. The agent sends the key on every check:

```
GET https://nibiashara.com/api/v1/verify?name=Blue%20Nile%20Ethiopian%20Restaurant
X-API-Key: <the key>
```

`RateLimit-Policy` on the answer then names `key` with `q=3000`, and `RateLimit` counts down from there. Details: Human operator registration, below.

### 2. Register the agent itself, then link a person

Use this when the agent runs with no one at a browser. It starts alone, with no captcha, no email and no form. It reaches the higher limit when a person links it.

```
POST https://nibiashara.com/agent/identity          {"type":"anonymous"}
POST https://nibiashara.com/oauth2/token            grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<identity_assertion>
GET  https://nibiashara.com/api/v1/verify?name=...  Authorization: Bearer <access_token>
POST https://nibiashara.com/agent/identity/claim    {"claim_token":"<claim_token>","email":"<the person's email>"}
```

The last call returns a `user_code`. Show it to the person and send them to https://nibiashara.com/agent/claim. We email them a second code and they enter both. The agent then trades its claim token for a new token pair at `/oauth2/token`. Until a person links it, a registered agent gets attribution and revocation but no extra volume: its checks still count against the shared 60 a day. Details: Agent self-registration, below.

Why this order: the key is one step for the person and works with any HTTP client. Self-registration is for when no one can open a browser at the start, and it needs the same person later.

## Paid: directory counts over x402 (no registration)

- `GET https://nibiashara.com/api?metro=&category=` answers HTTP 402 with x402 v2 terms (`PAYMENT-REQUIRED`): $0.02 in USDC on Base (`eip155:8453`) for listing counts by metro and category.
- Pay with any x402 client and retry with `PAYMENT-SIGNATURE`. The sale is fulfilled and settled by Ni Biashara Shelves (https://agents.nibiashara.biz, shelf `diaspora-metro-snapshot`); nibiashara.com passes your request through. No account; the wallet is the credential.
- The 402 names its resource as `https://agents.nibiashara.biz/shelf/diaspora-metro-snapshot`, not `https://nibiashara.com/api`, because Shelves writes the terms and settles the payment. nibiashara.com returns that answer unchanged and forwards `PAYMENT-SIGNATURE`, `X-PAYMENT` and `X-SHELF-PASS` on the paid retry. A client that only pays when the resource matches the address it called should call the Shelves address directly, with the same `metro` and `category` parameters.

## Agent self-registration (anonymous, no captcha, no email)

You are an agent. Follow these steps in order.

1. Discover: `GET https://nibiashara.com/.well-known/oauth-protected-resource` (resource `https://nibiashara.com`, protected API under `/api/v1/`, scope `directory.read`, bearer in the header), then `GET https://nibiashara.com/.well-known/oauth-authorization-server`. Its `agent_auth` block names the identity endpoint and `identity_types_supported: ["anonymous"]`.
2. Register: `POST https://nibiashara.com/agent/identity` with JSON `{"type":"anonymous"}`. The response holds `registration_id` and `identity_assertion` (a signed JWT, valid 10 minutes, usable once). Only `anonymous` registration is offered; any other type is refused.
3. Exchange: `POST https://nibiashara.com/oauth2/token` (form or JSON) with `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer` and `assertion=<identity_assertion>`. You get `access_token` (prefix `nbat_`, valid 1 hour) and `refresh_token` (prefix `nbrt_`, valid 90 days, single use).
4. Use: send `Authorization: Bearer <access_token>` on `GET https://nibiashara.com/api/v1/verify`. Never put a token in the URL; that is refused. A bad, expired or revoked token gets 401 with `WWW-Authenticate: Bearer resource_metadata="https://nibiashara.com/.well-known/oauth-protected-resource"`.
5. Refresh: `POST /oauth2/token` with `grant_type=refresh_token&refresh_token=<refresh_token>`. You get a new pair; the old refresh token stops working. Never retry with a refresh token you already used: presenting a used refresh token again revokes every token of your registration. Your quota stays with your registration, not with the token.
6. Revoke: `POST https://nibiashara.com/oauth2/revoke` with `token=<token>` (RFC 7009). Always answers 200.
7. Optional, link a human (claim): the registration response also gives you a `claim_token` (prefix `nbct_`, 30 days). Keep it secret like a password; never show it to anyone. `POST https://nibiashara.com/agent/identity/claim` with JSON `{"claim_token":"...","email":"..."}`. You get `claim_attempt.user_code` and `claim_attempt.verification_uri` (`https://nibiashara.com/agent/claim`). Show both to the person. We email them a separate 8-digit code; they enter both codes on that page. Then poll `POST /oauth2/token` with `grant_type=urn:workos:agent-auth:grant-type:claim&claim_token=...` no more than every 5 seconds (faster gets `slow_down`): `authorization_pending` until they finish, then a new token pair at the linked (claimed) tier. Codes expire in 15 minutes. One linked agent per email. Linking never gives rights over a business listing.

Linked (claimed) agents get 3,000 verify calls a month and are not held to the per-network limit. Limits for anonymous agents: 10 verify calls a day for the first two days, 20 a day until day 8, then 300 a month. Anonymous calls also share the 60-a-day per-network limit that applies without a token, so a registration adds attribution and revocation, not volume. Registration itself is limited per network. Search and the bulk listing JSON stay free and need no token.

## Human operator registration (higher tier, email-confirmed)

Registration is optional: every read above works without it. Registering an agent alone adds no volume. An API key from this section, or linking the agent to a person (step 7 above), is what lifts the `/api/v1/verify` limit.

- Audience: an agent's human operator. Each request needs a Cloudflare Turnstile token, so registration runs through the form at https://nibiashara.com/agents rather than from a headless agent.
- Registration endpoint: `POST https://nibiashara.com/api/v1/agents/signup` with JSON `{name, kind, homepage (optional), contact_email, consent, "cf-turnstile-response"}`. It returns `{api_key, account_id}` once; only a hash of the key is stored. One active key per email.
- Confirmation endpoint: `POST https://nibiashara.com/api/v1/agents/confirm` with JSON `{account_id, code, "cf-turnstile-response"}`. The 32-character code is emailed to `contact_email`. It is single-use, valid for 24 hours, with at most 5 attempts.
- Supported method: email verification (verified email), for the human operator tier.
- Credential type: API key. It starts at 0 checks a month and becomes 3,000 checks a month once confirmed.
- Credential use: send it as the `X-API-Key` request header on `GET https://nibiashara.com/api/v1/verify`. An invalid or revoked key gets 401; a used-up monthly quota gets 429.

## Errors

A limit you run into, a path that is not a route, and a method a route does not take are answered as `application/problem+json` (RFC 9457): `type`, `title`, `status` and `detail`, then the links that apply. `type` is one of the anchors below. On a limit, `error` repeats the short message older clients read. One 429 is not written by this API: the edge's burst block (see burst-blocked).

### rate-limited

429. The free daily limit per network is used up. `Retry-After` and `retry_after` give the seconds until it resets at 00:00 UTC. The two ways up are both linked: `signup` is the key route, which a person completes on https://nibiashara.com/agents, and `agent_identity` is the route an agent can call by itself to register (the limit rises once a person links it). `docs` is the section above on getting a higher limit, and `paid` is the paid directory counts.

### quota-used-up

429. An API key's monthly quota, or a registered agent's quota, is used up. `limit` names which one and how large it is. `Retry-After` gives the seconds until it resets.

### key-not-confirmed

429. The API key has not been confirmed, so its monthly quota is still 0. `confirm` is the route; a person finishes it on https://nibiashara.com/agents with the emailed code. Waiting does not change it.

### burst-blocked

429 from the edge, not from this API, after more than the `burst` limit. The block lasts 600 seconds. Its `type` is a Cloudflare address, not an anchor here, and it carries none of the links above. The numbers, the routes it covers and what the answer looks like are under Limits you can read.

### not-found

404. There is no API route at that path under `/api/v1/`. `docs` links the OpenAPI description, which lists every route.

### method-not-allowed

405. The route exists but does not take that method. The `Allow` header and `allow` list the methods it takes. The two sign-up routes take POST only.

## Business owner actions

Claiming a listing (https://nibiashara.com/claim) and asking for a new one (https://nibiashara.com/add) are done by people and confirmed by email. Ownership is checked before a claim is confirmed. Agents cannot claim or edit listings on someone's behalf.

An agent can tell an owner whether their listing is open. Results from search, MCP and A2A carry `claim_open` (true until the listing is claimed or verified) and `claim_url` (the page to claim it on, free). When `claim_open` is true, give the owner that link.

## Contact

Corrections and access questions: twe@nibiashara.biz
