API v1EXAMPLE DATARead only, included with every shop
Does AlleyPulse have an API and MCP server for AI agents?
Yes. AlleyPulse has a read only REST API and a remote MCP server, so an owner's AI assistant can ask what changed around their shop this week and get the same tiered items as the Monday email, each with a dated source link. Create a key in the app. The hosts, keys, addresses and responses shown on this page are examples.
Last updated: 26 October 2026
What can an AI assistant do with AlleyPulse?
It reads the same weekly issue you get by email, so you can ask your assistant instead of opening the inbox. It cannot change anything, and it only sees your own shops.
- Version
- v1, read only, included with every shop subscription
- REST base address
https://api.alleypulse.com/v1- MCP server
https://api.alleypulse.com/mcpover streamable HTTP- Sign in
- Bearer API key, read only. OAuth for MCP clients is not part of v1.
- Rate limit
- 60 requests per minute per key
- Data
- The same weekly issues as the email. Weekly, not real time.
- Machine readable
- An OpenAPI 3.1 file at
/v1/openapi.jsonand allms.txtfile.
| Question | MCP tool | REST endpoint |
|---|---|---|
| What changed around my shop this week? | get_latest_digest | GET /shops/{id}/issues/latest |
| What should I act on before Friday? | get_latest_digest (tier A) | GET /shops/{id}/issues/latest?tier=A |
| Did a new listing appear near me in the last month? | search_items | GET /issues/{id}/items?category=new_listing |
| What did issue 4 say about the street market? | get_item | GET /items/{id} |
| Does this address work with AlleyPulse? | check_coverage | POST /coverage-check |
How do I make the first request?
Create a read only key on the API and MCP screen in the app, then send it as a bearer token. These examples use the example key ap_live_EXAMPLE, and example shop and issue ids.
curl
# Example only. The key is a placeholder: use your own key.
curl https://api.alleypulse.com/v1/shops/shp_EXAMPLE01/issues/latest \
-H "Authorization: Bearer ap_live_EXAMPLE" Read the latest issue for one shop. Replace the placeholder key and shop id with your own.
JavaScript
// Example only. The key is a placeholder: use your own key.
const res = await fetch(
"https://api.alleypulse.com/v1/shops/shp_EXAMPLE01/issues/latest?tier=A",
{ headers: { Authorization: "Bearer ap_live_EXAMPLE" } }
);
if (!res.ok) throw new Error((await res.json()).error.message);
const issue = await res.json();
for (const item of issue.items) {
console.log(item.tier, item.distance_m + " m", item.headline);
} The same request with fetch. Filter to tier A to get only what needs action this week.
MCP
claude mcp add --transport http alleypulse https://api.alleypulse.com/mcp --header "Authorization: Bearer ap_live_EXAMPLE" One command registers the server in Claude Code. Other clients are in the connect section below.
{
"id": "iss_EXAMPLE06",
"shop_id": "shp_EXAMPLE01",
"number": 6,
"published_at": "2026-10-26T07:00:00Z",
"is_baseline": false,
"scan": {
"radius_m": 1000,
"listings_scanned": 137,
"scanned_on": "2026-10-25",
"compared_with": "2026-10-18"
},
"tier_counts": { "A": 3, "B": 4, "C": 3 },
"summary": "A nearby café started a morning promo aimed at your commuters, Saturday's street market will bring extra people past your door, and a bakery's rating dipped after complaints about waiting.",
"items_url": "https://api.alleypulse.com/v1/issues/iss_EXAMPLE06/items",
"items": [
{ "id": "itm_EXAMPLE01", "tier": "A", "headline": "A café 200 m away is offering two for one flat whites before 9am" }
]
} The example issue has 10 items; the response above shows one. Fetch the rest from items_url.
Which REST endpoints are there?
Every data endpoint is a GET, so it is safe to retry. The two POST endpoints do not change your shop data. Responses are JSON, and the version is in the path.
| Method | Path | Returns | Notes |
|---|---|---|---|
| GET | /shops | The shops this key can read. | No parameters. |
| GET | /shops/{id} | One shop: name, address as you entered it, business type, radius, time zone, next issue date. | |
| GET | /shops/{id}/issues | Issues, newest first, without items. | since, limit (default 10, maximum 52), cursor |
| GET | /shops/{id}/issues/latest | The latest published issue with its items. | Optional tier filters the embedded items. |
| GET | /issues/{id} | One issue: summary, tier counts, scan facts, items. | |
| GET | /issues/{id}/items | Items of one issue, paginated. | tier (A, B or C), category, max_distance_m, cursor |
| GET | /items/{id} | One item, with every field below. | |
| POST | /coverage-check | Whether an address is supported, has few listings, or is not supported. | Address goes in the JSON body, never a query string. Free, limited to 10 requests per minute. |
| POST | /webhooks | Subscribes an HTTPS endpoint to issue.published. Returns the signing secret once. | Also GET /webhooks and DELETE /webhooks/{id}. |
Lists use cursor pagination: pass next_cursor from one response as cursor in the next request, and stop when has_more is false.
{
"data": [ { "id": "iss_EXAMPLE06", "number": 6, "published_at": "2026-10-26T07:00:00Z" } ],
"next_cursor": "cur_EXAMPLE",
"has_more": true
} Address check example. The address is sent in the body so it never appears in a URL or an access log.
curl https://api.alleypulse.com/v1/coverage-check \
-H "Authorization: Bearer ap_live_EXAMPLE" \
-H "Content-Type: application/json" \
-d '{"address": "12 Sample Street, Sampletown", "radius_m": 1000}' What does an item contain?
Every item answers the same five questions as the email: what changed, where, why it matters, what to do this week, and the source. There is no field for review text or for the names of nearby businesses.
| Field | Type | Meaning |
|---|---|---|
id, issue_id, issue_number | string, string, integer | Identifiers. Issue number 1 is the baseline issue. |
tier | A, B or C | A is act this week, B is worth knowing, C is skim. |
category | enum | competitor_promo, competitor_rating, competitor_posts, new_listing, marked_closed, local_event, local_news, search_interest, weather, industry_trend, customer_mention, local_buzz. New values can be added within v1. |
headline | string | One line, in plain words, with an anonymized peer such as a café 200 m away. |
what_changed | string | What we saw and when, without quoting reviews. |
distance_m | integer or null | Meters from your address. Null for regional items such as weather or search interest. |
why_it_matters | string or null | Why it could matter to a shop like yours. Null for some tier C items. |
what_to_do | string | One suggested action for this week. |
source_url, source_type | string, enum | A dated source link and its kind: google_maps_listing, google_maps_post, events_calendar, news_page, weather_forecast, search_interest, trade_press or social_post. |
first_seen | date | The day our weekly scan first saw it. Not an opening date. |
{
"id": "itm_EXAMPLE01",
"issue_id": "iss_EXAMPLE06",
"issue_number": 6,
"tier": "A",
"category": "competitor_promo",
"headline": "A café 200 m away is offering two for one flat whites before 9am",
"what_changed": "New post on its Google Maps listing, valid weekdays until Friday 6 November.",
"distance_m": 200,
"why_it_matters": "It targets the same commuters as your busiest window, 7 to 9am.",
"what_to_do": "Do not match the price. Put your loyalty card or a coffee and pastry pairing in the window, and compare 7 to 9am sales with last week.",
"source_url": "https://source.alleypulse.example/itm_EXAMPLE01",
"source_type": "google_maps_post",
"first_seen": "2026-10-24"
} How do keys and sign in work?
- Send
Authorization: Bearer ap_live_...on every request. Keys never go in a URL. - A key is shown once when you create it and stored only as a hash. If you lose it, create a new one.
- One scope in v1,
read. A key can read all your shops or only the shops you choose. - Revoke a key in the app at any time. Requests with it fail straight away.
- Keep keys out of browser code and public repositories. OAuth sign in for MCP clients is not part of v1.
What are the limits, versions and errors?
The limit is 60 requests per minute per key. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, and a 429 response adds Retry-After. Within v1 we may add fields and enum values without notice, so ignore what you do not recognize. Breaking changes ship as v2, with the old version kept running for at least 6 months.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A parameter is missing or has the wrong shape. |
| 401 | unauthorized | No key, a wrong key or a revoked key. |
| 403 | forbidden | The key has no access to that shop. |
| 404 | not_found | The shop, issue or item does not exist, or belongs to someone else. |
| 422 | unsupported_address | The address check failed: Russia, Belarus or no listings found. |
| 429 | rate_limited | More than 60 requests in a minute for this key. |
| 503 | unavailable | A weekly scan is being published. Retry with the Retry-After header. |
{
"error": {
"code": "rate_limited",
"message": "The limit is 60 requests per minute for this key. Retry after 12 seconds.",
"request_id": "req_EXAMPLE"
}
} How do webhooks work?
There is one event, issue.published, sent when the weekly issue for one of your shops is ready. The payload holds only ids, so fetch the items with the API. It is a weekly event, not a stream of live changes.
{
"id": "evt_EXAMPLE01",
"type": "issue.published",
"created_at": "2026-10-26T07:00:12Z",
"data": {
"shop_id": "shp_EXAMPLE01",
"issue_id": "iss_EXAMPLE06",
"issue_number": 6,
"is_baseline": false
}
} Each delivery is signed. The header holds a timestamp and an HMAC SHA256 of t + "." + raw body, keyed with the endpoint secret you receive once when you subscribe. Reject deliveries whose timestamp is older than five minutes. Failed deliveries are retried up to 5 times over a day.
AlleyPulse-Signature: t=1793000412,v1=EXAMPLE_HEX_DIGEST // Example only. The secret is a placeholder.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const signed = parts.t + "." + rawBody;
const expected = createHmac("sha256", secret).update(signed).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 ?? "");
return a.length === b.length && timingSafeEqual(a, b);
} Which MCP tools are there?
A remote MCP server over streamable HTTP, signed in with the same read only API key. Every tool is read only and safe to repeat. No tool sends email, edits a shop or posts anywhere.
https://api.alleypulse.com/mcp | Tool | Arguments | Returns |
|---|---|---|
list_shops | none | The shops this key can read, with ids. |
get_latest_digest | shop_id (optional), tier (optional) | The latest issue with its items. If shop_id is left out and the key reads one shop, that shop is used. |
list_issues | shop_id, limit | Recent issues, newest first, without items. |
get_item | item_id | One item with every field. |
search_items | query, shop_id, since, tier | Items whose headline or text match the query, newest first. Searches only your own issues. |
check_coverage | address | Whether the address is supported, has few listings or is not supported. |
One resource is available: alleypulse://shops/{id}/issues/latest, the latest issue of a shop, so a client can attach it to a conversation.
How do I connect a client?
Copy a snippet, replace the placeholder key with your own, and restart the client. Client configuration formats differ and change, so treat these as examples and check your client's current documentation.
Claude Code
claude mcp add --transport http alleypulse https://api.alleypulse.com/mcp --header "Authorization: Bearer ap_live_EXAMPLE" Run this in a terminal. Claude Code lists the server in its MCP settings afterwards.
Claude Desktop
{
"mcpServers": {
"alleypulse": {
"command": "npx",
"args": [
"-y", "mcp-remote",
"https://api.alleypulse.com/mcp",
"--header", "Authorization: Bearer ${AP_API_KEY}"
],
"env": { "AP_API_KEY": "ap_live_EXAMPLE" }
}
}
} Claude Desktop reaches remote servers through its connectors screen or a local bridge such as mcp-remote. This example uses the bridge in claude_desktop_config.json.
Cursor
{
"mcpServers": {
"alleypulse": {
"url": "https://api.alleypulse.com/mcp",
"headers": { "Authorization": "Bearer ap_live_EXAMPLE" }
}
}
} Save as .cursor/mcp.json in your project, or in your home folder for all projects.
Generic mcp.json
{
"mcpServers": {
"alleypulse": {
"type": "http",
"url": "https://api.alleypulse.com/mcp",
"headers": { "Authorization": "Bearer ap_live_EXAMPLE" }
}
}
} Many clients accept this shape. Field names differ between clients, so check yours.
What does a conversation look like?
An owner asks an assistant that has the MCP server connected. Here is an example. The assistant calls one tool and answers from the issue.
What changed around my shop this week?
get_latest_digest with {"tier": "A"}, returns 3 items from issue 6.
Three things need action this week, from Monday's issue (example data):
- A café 200 m away is offering two for one flat whites before 9am on weekdays until 6 November. It targets your 7 to 9am commuters, so the suggestion is to skip the price match and promote your loyalty card or a coffee and pastry pairing. Source: its Google Maps listing post, first seen 24 October.
- A street market is on Sample Square this Saturday from 10:00 to 16:00, and Market Lane is closed to traffic. Consider moving Saturday's delivery to Friday. Source: the city events calendar, published 21 October.
- A bakery 0.8 km away dropped from 4.6 to 4.4 stars after 9 new reviews in 7 days, and 5 of them mention waiting. Quick service is a gap you can fill, so make order ahead visible. Source: its Google Maps rating and review count.
This is the issue from 26 October. AlleyPulse is weekly, so new changes will appear next Monday. Each item above links to its source.
What are the data rules?
- No raw review text. Items give counts, rating changes and short summarized themes, never quoted reviews or reviewer names.
- Anonymized peers. Nearby businesses appear as a café 200 m away, not by name.
- Sources linked. Every item has a source link, a source type and the day we first saw it.
- Weekly, not real time. Data is a weekly snapshot and can be hours to days old when you read it.
- The first issue is a baseline. It is flagged
is_baseline: trueand shows what is around you, not what changed. New and closed items begin in issue 2 and are reliable from about week 3 to 4. - New on Google Maps is not an opening. A closed item means marked closed on Google at scan time.
- Same coverage as the email. Wherever Google Maps has business listings, except Russia and Belarus. See coverage.
- For your own use. Use items in your own tools and assistants. The terms cover reuse and resale.
How do I get access, and what does it cost?
Every shop subscription includes read only API and MCP access to the same data as the email, at 60 requests per minute per key and up to 5 active keys per account. The address check is free for everyone at a lower limit. There is no extra charge for the API. The $39 per month per shop on the pricing page is a placeholder price, and we are not charging yet.
API and MCP questions
Does AlleyPulse have an API?
Yes. AlleyPulse has a read only REST API with bearer API keys, base address https://api.alleypulse.com/v1, that returns the same weekly issues and items as the Monday email. Every shop subscription includes it, and you create keys in the app.
Does AlleyPulse have an MCP server, and which AI assistants can connect?
Yes. The MCP server is a remote server over streamable HTTP at https://api.alleypulse.com/mcp, signed in with an API key. Any assistant or editor that supports remote MCP servers can connect, such as Claude Code, Claude Desktop through a bridge, or Cursor. Setup snippets for each are on this page, and client formats differ.
Is the API data the same as the weekly email?
Yes. The email, the web view, the API and the MCP server read the same weekly issues, so an assistant sees the same items, tiers, distances, suggested actions and source links you see in your inbox. It is a weekly snapshot, not a live feed.
Can my AI assistant ask what changed around my shop this week?
Yes, that is the main use. An assistant with the MCP server connected can call a tool such as get_latest_digest and answer from the latest issue, for example by listing the tier A items you should act on this week and quoting each source. Because issues are weekly, asking again before the next Monday returns the same issue.
Can the API or MCP server change anything or send messages?
No. Data access is read only. There are no endpoints to edit your shop, send email or post anywhere. The only writes are creating and deleting webhook endpoints, and the address check, which does not store your address.
How are API keys protected, and how do I revoke one?
A key is shown once when you create it and stored only as a hash on our side. Each key is read only and can be limited to chosen shops. You can revoke a key at any time in the app, and requests with a revoked key fail straight away. Keep keys out of browser code and public repositories.
What are the rate limits?
The limit is 60 requests per minute per key. Responses carry the headers X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, and a 429 response adds Retry-After. The free address check has a stricter limit of 10 requests per minute.
Does the API return Google review text or competitor names?
No. Items report counts, rating changes and short summarized themes, and nearby businesses are anonymized, for example as a café 200 m away. Every item links to its source type and a dated source link instead of copying third party text.
How much does API and MCP access cost?
Nothing extra. Read only API and MCP access is included with each shop subscription, with the same data as the email. The subscription price is $39 per month per shop, and we are not charging yet. The free address check is open to everyone at a lower limit.