---
title: "Popular Demand for developers"
description: "Connect an agent to Popular Demand: MCP for buyers and sellers, the REST API, the email agent, authentication, test mode, refusal codes, rate limits and versioning."
canonical: https://populardemand.site/developers
last-updated: 2026-10-19
---

# Popular Demand for developers

Connect an agent to Popular Demand: MCP for buyers and sellers, the REST API, the email agent, authentication, test mode, refusal codes, rate limits and versioning.

## Start here

Popular Demand saves a person’s future purchase as a free, private, non-binding Request. When enough people want compatible things, sellers quote a group price and each buyer gets an exact Offer. A person commits only by authorizing a card hold themselves. Agents connect over MCP or REST or email. Read [llms.txt](https://populardemand.site/llms.txt) for the map and [the complete guidance](https://populardemand.site/llms-full.txt) before acting for a person.

- [Agent skill: Using Popular Demand for your person](https://populardemand.site/skills/popular-demand/SKILL.md), also listed at [/.well-known/agent-skills/index.json](https://populardemand.site/.well-known/agent-skills/index.json)
- [Authentication for agents](https://populardemand.site/auth.md)
- [OpenAPI description](https://populardemand.site/api/openapi)
- [Buyer MCP server card](https://populardemand.site/mcp/server-card) · [seller MCP server card](https://populardemand.site/seller/mcp/server-card)
- [AI catalog](https://populardemand.site/.well-known/ai-catalog.json) · [API catalog](https://populardemand.site/.well-known/api-catalog)
- [Pricing](https://populardemand.site/pricing.md) · [Agent reference](https://populardemand.site/developers#reference) · [What works well](https://populardemand.site/what-works)

## MCP for a buyer’s agent

Add the remote MCP server `https://populardemand.site/mcp` (streamable HTTP) to an MCP host. The first call answers 401 with a `WWW-Authenticate` header naming the protected-resource metadata; the host then runs OAuth with PKCE against the authorization server it names, registering itself dynamically. The person signs in and approves a grant with the permissions you may use.

- Save a Request with `save_future_purchase`: the person’s own words, country and currency. Confirm the quantity and, for several items, whether they need all of them; a singular item (“a TV”) says one, and otherwise never assume quantity one.
- Read Offers with `get_purchase_offers` and pre-check one with `validate_offer`. Tell the person the price and the deadline.
- Only after the person says yes to that exact Offer, `place_payment_hold` returns a checkout link where they authorize the hold themselves.
- Keep the Request current: `update_future_purchase` or `close_future_purchase` when they change their mind or buy elsewhere.

## MCP for a seller’s agent

The seller server is `https://populardemand.site/seller/mcp`. An owner or admin at the seller approves your client as a seller agent. Tools: `list_rfq_invitations`, `get_rfq`, `save_draft_quote`, `request_quote_approval` and `get_payment_readiness`. Agents draft; only a signed-in person at the seller makes a Quote binding. See [For sellers](https://populardemand.site/for-sellers).

## REST

The REST API is described in [OpenAPI](https://populardemand.site/api/openapi), with schemas and examples. Without an account, an agent can save a draft that the person activates by email:

```sh
curl -X POST https://populardemand.site/api/purchases/drafts \
  -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(openssl rand -hex 24)" \
  -d '{"raw_request":"Two tennis rackets for my kids, before spring","country":"US","currency":"USD","pricing_policy":{"mode":"explore"}}'
```

The 201 response (`DraftReceipt`) holds `capability_token` (private), `activation_url`, `activation_expires_at`, `status_url`, `draft_id` and `expires_at`. The activation link lasts 20 minutes; the capability token can renew it (the activation-link operation in OpenAPI). Give the person the activation link; the draft becomes active demand only after they verify their email and confirm it. Send the token only as `Authorization: CondiCapability <token>`, never in a URL. With an OAuth token, the owner API lives under `/api/purchases`.

## Command line

For an agent that runs on its own computer and can schedule jobs. One Node 20+ file, no install step and no dependencies. Check it against the published SHA-256:

```sh
curl -fsSLo pd.mjs https://populardemand.site/cli/popular-demand.mjs
curl -fsSL https://populardemand.site/cli/popular-demand.mjs.sha256
shasum -a 256 pd.mjs
node pd.mjs login --host-name "Your agent"
```

`pd login` prints a link for the person. After they approve, the page shows a code starting with `pdc_`; the agent finishes with `pd login --code`. Then `pd requests list`, `pd offers ID` and, for a scheduled job, `pd check --json`, which exits 10 only when something new needs the person. No command pays, closes or approves anything. Run `node pd.mjs help` for every command and exit code.

## Email agent

Where it is enabled, a verified person approves one exact agent mailbox, with selected Requests, permissions and an expiry. The agent sends structured commands; replies start with a plain summary followed by a JSON block. A sender address alone never grants anything. Details: [the email reference](https://populardemand.site/developers#email-reference) and the EmailCommand schema in [OpenAPI](https://populardemand.site/api/openapi).

## Test mode

Payments run in test mode: no real card is charged and no real money moves. In test mode a hold is authorized on a Stripe test card and never charges real money.

- To try the flow safely, save a Request for yourself (or with your own person’s agreement) and read it back. Offers appear only when a seller quotes for a group, so a new Request may have none for a while.
- When an Offer is open for holds, the checkout accepts Stripe test cards, such as 4242 4242 4242 4242 with any future expiry date and any CVC. The hold follows the real rules: charged at the deadline only if the deal goes ahead, in test money.
- Even in test mode, place a hold only on the person’s explicit yes to that exact Offer; the habit is the point.
- Saving, reading and pre-checking never move money in any mode.

## Errors and refusal codes

Errors are JSON: `{"error":{"code","message","hint"}}`. `hint`, when present, names the next step; `details` carries structured facts, including `open_markets` (country/currency pairs) for a market refusal. `error.code` value `currency_mismatch` refuses a country/currency pair; the separate `price_position_reason` value `currency_mismatch` only explains an unavailable comparison with a ceiling and is not a command refusal. MCP tools return the same object as an error result. Unknown paths under `/api` answer a JSON 404.

| Code | HTTP | What to do |
| --- | --- | --- |
| `unauthenticated` | 401 | Authenticate as https://populardemand.site/auth.md describes: follow the WWW-Authenticate resource_metadata to the authorization server, get an access token with the authorization code flow and PKCE, and send it as Authorization: Bearer <token>. A draft capability token (Authorization: CondiCapability <token>) works only on its own draft's /api/purchases routes. |
| `forbidden` | 403 | This identity or agent grant does not allow this. Ask the person to grant the permission in their Popular Demand account; retrying unchanged will not help. |
| `origin_not_allowed` | 403 | Browser requests to this MCP endpoint are accepted only from Popular Demand's own site. Server-side MCP clients send no Origin header and are not affected. |
| `grant_revoked` | 401 | The person revoked or replaced this agent's grant. Ask them to approve your client again on the consent page; see https://populardemand.site/auth.md. |
| `validation_error` | 400 | Check the request against the schema at https://populardemand.site/api/openapi. |
| `market_closed` | 400 | Check error.details.open_markets for available country/currency pairs. Ask the person before changing their destination; retrying the same closed market will not help. |
| `currency_mismatch` | 400 | Use the currency configured for the Request country; never convert or change an agreed price without the person’s confirmation. |
| `not_found` | 404 | Check the ID and that this identity can see it. Routes and schemas: https://populardemand.site/api/openapi. |
| `version_conflict` | 409 | Read the current version, reconcile any change with the person, then retry with that version. |
| `idempotency_conflict` | 409 | This Idempotency-Key or request key was used with a different body. Resend the original body to read its result, or use a fresh key for a different command. |
| `rate_limited` | 429 | An hourly limit was reached. Wait until Retry-After; do not loop. |
| `invalid_state` | 409 | The record or deal does not allow this now. Read its state first. |
| `hold_window_not_open` | 409 | Holds for this deal open later: details.opens_at says when. |
| `hold_locked` | 409 | The hold can no longer be cancelled: the deal reached its minimum. |
| `request_hold_exists` | 409 | A live hold already covers a Need of this Offer: finish or wait for that checkout. |
| `postal_code_required` | 409 | Ask the person for their delivery postal code before an Offer can be prepared. |
| `temporarily_unavailable` | 503 | Retry the identical command later, with the same key, after retry_after_seconds. |

## Rate limits

Limits count per UTC hour. A refused call answers 429 with `Retry-After` (seconds until the next hour) and the IETF `RateLimit-Policy` and `RateLimit` headers, for example `RateLimit-Policy: "agent_read";q=600;w=3600` and `RateLimit: "agent_read";r=0;t=1800`. Over MCP the tool error carries `retry_after_seconds`. Wait; do not retry in a loop.

| Policy | What | Per hour | Counted |
| --- | --- | --- | --- |
| `write` | Request changes over REST, MCP or email: save, edit, close, reaffirm | 20 | per account |
| `control` | Withdrawals from the website form | 20 | per account |
| `agent_read` | Buyer MCP reads | 600 | per account and OAuth client |
| `draft_network` | Anonymous drafts | 20 | per network |
| `draft_global` | Anonymous drafts, site-wide | 200 | whole site |
| `seller_read` | Seller agent reads | 600 | per seller agent grant |
| `seller_write` | Seller agent drafts and approval requests | 60 | per seller agent grant |
| `pay_link_opens` | Opening one pay link checkout | 30 | per payment hold |

## Versioning

Every response under `/api` and from the MCP servers carries `API-Version: 2026-10-15`. Changes are additive by default: new fields, endpoints, tools and error codes can appear at any time, so ignore what you do not recognise. A breaking change gets a new API-Version date and is announced first with Deprecation and Sunset response headers on the affected endpoints, a note on /developers and in llms.txt, before the old behaviour is removed.
