Skip to content

Commit 1b333ba

Browse files
authored
docs: Flesh out notifications docs (#1279)
* docs: rewrite notifications page with accurate API surface and cleaner copy * docs: use Alice and Bob vanity addresses in examples * docs: apply feedback — rebrand to Base Dashboard, angle bracket placeholders, expanded errors, rate limits
1 parent cc2de19 commit 1b333ba

1 file changed

Lines changed: 120 additions & 199 deletions

File tree

Lines changed: 120 additions & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -1,278 +1,199 @@
11
---
22
title: "Notifications"
3-
description: "Send notifications to your mini app users by wallet address using the Base.dev REST API."
3+
description: "Send in-app notifications to your app's users through the Base Dashboard REST API."
44
---
55

6-
Send notifications to users who have installed your app in the Base App using the Base.dev REST API. No webhooks, no database, no token management.
7-
86
<Note>
9-
Notifications are scoped to your app. Your API key only returns users who opted into your app and can only send to those users.
7+
Notifications are delivered through the **Base App** only. Users who interact with your app on other platforms will not receive notifications through this API.
108
</Note>
119

12-
## Quickstart
13-
14-
<Steps>
15-
<Step title="Generate your API key">
16-
Go to your project on [Base.dev](https://base.dev), open **Settings > API Key**, and generate a new key. Add it to your environment:
17-
18-
```bash .env
19-
BASE_DEV_API_KEY=bdev_your_api_key_here
20-
```
21-
22-
<Warning>
23-
Never commit API keys to version control.
24-
</Warning>
25-
</Step>
26-
27-
<Step title="Query your opted-in users">
28-
<RequestExample>
29-
```bash cURL
30-
curl "https://www.base.dev/v1/notifications/app/users?app_url=https://your-app.com&notification_enabled=true" \
31-
-H "x-api-key: $BASE_DEV_API_KEY"
32-
```
33-
</RequestExample>
34-
35-
<ResponseExample>
36-
```json Response
37-
{
38-
"success": true,
39-
"users": [
40-
{ "address": "0xc0c3132DFc4929bb6F68FA76B8fB379fF8c5bE74", "notificationsEnabled": true },
41-
{ "address": "0x4e2bC8463190fBa0C5bF5921a98552f4728E3e9f", "notificationsEnabled": true }
42-
]
43-
}
44-
```
45-
</ResponseExample>
46-
</Step>
47-
48-
<Step title="Send a notification">
49-
<RequestExample>
50-
```bash cURL
51-
curl -X POST "https://www.base.dev/v1/notifications/send" \
52-
-H "x-api-key: $BASE_DEV_API_KEY" \
53-
-H "Content-Type: application/json" \
54-
-d '{
55-
"app_url": "https://your-app.com",
56-
"wallet_addresses": ["0xc0c3132DFc4929bb6F68FA76B8fB379fF8c5bE74"],
57-
"title": "Hey from your app!",
58-
"message": "You have a new reward waiting.",
59-
"target_path": "/rewards"
60-
}'
61-
```
62-
</RequestExample>
63-
64-
<ResponseExample>
65-
```json Response
66-
{
67-
"success": true,
68-
"results": [
69-
{ "walletAddress": "0xc0c3132DFc4929bb6F68FA76B8fB379fF8c5bE74", "sent": true }
70-
],
71-
"sentCount": 1,
72-
"failedCount": 0
73-
}
74-
```
75-
</ResponseExample>
76-
</Step>
77-
</Steps>
78-
79-
## Send from code
80-
81-
```typescript app/api/notify/route.ts
82-
import { NextResponse } from "next/server";
83-
84-
const API_KEY = process.env.BASE_DEV_API_KEY!;
85-
const APP_URL = process.env.NEXT_PUBLIC_URL!;
86-
87-
export async function POST(req: Request) {
88-
const { title, message, targetPath } = await req.json();
89-
90-
const usersRes = await fetch(
91-
`https://www.base.dev/v1/notifications/app/users?app_url=${APP_URL}&notification_enabled=true`,
92-
{ headers: { "x-api-key": API_KEY } }
93-
);
94-
const { users } = await usersRes.json();
95-
const addresses = users.map((u: { address: string }) => u.address);
96-
97-
const sendRes = await fetch("https://www.base.dev/v1/notifications/send", {
98-
method: "POST",
99-
headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
100-
body: JSON.stringify({ app_url: APP_URL, wallet_addresses: addresses, title, message, target_path: targetPath }),
101-
});
102-
103-
return NextResponse.json(await sendRes.json());
104-
}
10+
The Notifications API lets you send in-app notifications to users who have pinned your app and opted in to notifications. Two REST endpoints handle the full workflow: fetch your audience's wallet addresses, then send targeted or broadcast messages.
11+
12+
## Prerequisites
13+
14+
- A project on [Base Dashboard](https://dashboard.base.org) with your app URL registered
15+
- An API key generated from **Settings > API Key** in your Base Dashboard project
16+
17+
## Quick start
18+
19+
Both endpoints require your API key in the `x-api-key` header.
20+
21+
<Info>
22+
The notification endpoints share a rate limit of **10 requests per minute per IP**. Requests to either endpoint count toward the same limit. Exceeding it returns a `429 Too Many Requests` response.
23+
</Info>
24+
25+
Fetch the wallet addresses of users who have opted in to notifications for your app:
26+
27+
```bash title="Get users with notifications enabled"
28+
curl "https://dashboard.base.org/api/v1/notifications/app/users?app_url=<your-app-url>&notification_enabled=true" \
29+
-H "x-api-key: <your-api-key>"
10530
```
10631

107-
## Scheduled notifications
108-
109-
Use [Vercel Cron Jobs](https://vercel.com/docs/cron-jobs) to send notifications on a recurring schedule.
110-
111-
```typescript app/api/cron/notify/route.ts
112-
import { NextResponse } from "next/server";
113-
114-
const API_KEY = process.env.BASE_DEV_API_KEY!;
115-
const APP_URL = process.env.NEXT_PUBLIC_URL!;
116-
117-
export async function GET(req: Request) {
118-
if (req.headers.get("Authorization") !== `Bearer ${process.env.CRON_SECRET}`) {
119-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
120-
}
121-
122-
const usersRes = await fetch(
123-
`https://www.base.dev/v1/notifications/app/users?app_url=${APP_URL}&notification_enabled=true`,
124-
{ headers: { "x-api-key": API_KEY } }
125-
);
126-
const { users } = await usersRes.json();
127-
const addresses = users.map((u: { address: string }) => u.address);
128-
129-
const sendRes = await fetch("https://www.base.dev/v1/notifications/send", {
130-
method: "POST",
131-
headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
132-
body: JSON.stringify({
133-
app_url: APP_URL,
134-
wallet_addresses: addresses,
135-
title: "Daily reminder",
136-
message: "Check in today to keep your streak alive.",
137-
target_path: "/streak",
138-
}),
139-
});
140-
141-
return NextResponse.json(await sendRes.json());
32+
```json title="Response"
33+
{
34+
"success": true,
35+
"users": [
36+
{ "address": "0xA11ce00000000000000000000000000000000000", "notificationsEnabled": true },
37+
{ "address": "0xB0B0000000000000000000000000000000000000", "notificationsEnabled": true }
38+
]
14239
}
14340
```
14441

145-
Register the schedule in `vercel.json`:
42+
Send a notification to one or more of those addresses. The `target_path` sets the route within your app that opens when the user taps the notification:
43+
44+
```bash title="Send a notification"
45+
curl -X POST "https://dashboard.base.org/api/v1/notifications/send" \
46+
-H "x-api-key: <your-api-key>" \
47+
-H "Content-Type: application/json" \
48+
-d '{
49+
"app_url": "<your-app-url>",
50+
"wallet_addresses": ["<wallet-address>"],
51+
"title": "<title>",
52+
"message": "<message>",
53+
"target_path": "<target-path>"
54+
}'
55+
```
14656

147-
```json vercel.json
57+
```json title="Response"
14858
{
149-
"crons": [{ "path": "/api/cron/notify", "schedule": "0 9 * * *" }]
59+
"success": true,
60+
"results": [
61+
{ "walletAddress": "0xA11ce00000000000000000000000000000000000", "sent": true }
62+
],
63+
"sentCount": 1,
64+
"failedCount": 0
15065
}
15166
```
15267

15368
## API reference
15469

155-
Both endpoints require your API key in the `x-api-key` header.
156-
15770
### GET /v1/notifications/app/users
15871

159-
```
160-
GET https://www.base.dev/v1/notifications/app/users
72+
Returns users who have pinned your app, with optional filtering by notification opt-in status. Results are paginated.
73+
74+
```http
75+
GET https://dashboard.base.org/api/v1/notifications/app/users
16176
```
16277

163-
**Query parameters**
78+
#### Query parameters
16479

16580
<ParamField query="app_url" type="string" required>
166-
Your mini app URL as registered on Base.dev.
81+
Your app URL as registered on the Base Dashboard.
16782
</ParamField>
16883

16984
<ParamField query="notification_enabled" type="boolean">
170-
Set to `true` to return only users who have enabled notifications.
85+
Set to `true` to return only users who have enabled notifications for your app.
86+
</ParamField>
87+
88+
<ParamField query="cursor" type="string">
89+
Pagination cursor returned from a previous response. Omit for the first page.
90+
</ParamField>
91+
92+
<ParamField query="limit" type="integer">
93+
Maximum users per page. Capped at 100.
17194
</ParamField>
17295

173-
**Response**
96+
#### Response
17497

17598
<ResponseField name="success" type="boolean">
17699
Whether the request succeeded.
177100
</ResponseField>
178101

179102
<ResponseField name="users" type="array">
180-
List of user objects.
181-
182-
<Expandable title="User properties">
183-
<ResponseField name="address" type="string">
184-
The user's wallet address.
185-
</ResponseField>
186-
<ResponseField name="notificationsEnabled" type="boolean">
187-
Whether the user has enabled notifications for your app.
188-
</ResponseField>
189-
</Expandable>
103+
Users who have pinned your app.
190104
</ResponseField>
191105

106+
<ResponseField name="users[].address" type="string">
107+
The user's wallet address.
108+
</ResponseField>
109+
110+
<ResponseField name="users[].notificationsEnabled" type="boolean">
111+
Whether the user has enabled notifications for your app.
112+
</ResponseField>
113+
114+
<ResponseField name="nextCursor" type="string">
115+
Cursor for the next page. Absent when no more results exist.
116+
</ResponseField>
117+
118+
119+
120+
---
121+
192122
### POST /v1/notifications/send
193123

194-
```
195-
POST https://www.base.dev/v1/notifications/send
124+
Sends an in-app notification to one or more wallet addresses.
125+
126+
```http
127+
POST https://dashboard.base.org/api/v1/notifications/send
196128
```
197129

198-
**Request body**
130+
#### Request body
199131

200132
<ParamField body="app_url" type="string" required>
201-
Your mini app URL as registered on Base.dev.
133+
Your app URL as registered on the Base Dashboard.
202134
</ParamField>
203135

204136
<ParamField body="wallet_addresses" type="string[]" required>
205-
Wallet addresses to notify. Maximum 1,000 per request.
137+
Wallet addresses to notify. Minimum 1, maximum 1,000 per request.
206138
</ParamField>
207139

208140
<ParamField body="title" type="string" required>
209-
Notification title.
141+
Notification title. Maximum 30 characters.
210142
</ParamField>
211143

212144
<ParamField body="message" type="string" required>
213-
Notification body text.
145+
Notification body text. Maximum 200 characters.
214146
</ParamField>
215147

216148
<ParamField body="target_path" type="string">
217-
Relative path to open when the user taps the notification (for example, `/leaderboard`). Omit to open your app at its root.
149+
Path to open when the user taps the notification, such as `/rewards`. Must start with `/` if provided. Maximum 500 characters. Omit to open your app at its root URL.
218150
</ParamField>
219151

220-
**Response**
152+
#### Response
221153

222154
<ResponseField name="success" type="boolean">
223-
Whether the request completed.
155+
`true` only when every address in the request delivered successfully.
224156
</ResponseField>
225157

226158
<ResponseField name="results" type="array">
227159
Per-address delivery status.
160+
</ResponseField>
161+
162+
<ResponseField name="results[].walletAddress" type="string">
163+
The targeted wallet address.
164+
</ResponseField>
228165

229-
<Expandable title="Result properties">
230-
<ResponseField name="walletAddress" type="string">
231-
The wallet address targeted.
232-
</ResponseField>
233-
<ResponseField name="sent" type="boolean">
234-
Whether delivery succeeded for this address.
235-
</ResponseField>
236-
</Expandable>
166+
<ResponseField name="results[].sent" type="boolean">
167+
Whether delivery succeeded for this address.
168+
</ResponseField>
169+
170+
<ResponseField name="results[].failureReason" type="string">
171+
Present when `sent` is `false`. Possible values: `user has not saved this app`, `user has notifications disabled`.
237172
</ResponseField>
238173

239174
<ResponseField name="sentCount" type="number">
240-
Total notifications sent.
175+
Total notifications delivered successfully.
241176
</ResponseField>
242177

243178
<ResponseField name="failedCount" type="number">
244-
Total notifications failed.
179+
Total notifications that failed to deliver.
245180
</ResponseField>
246181

247-
**Errors**
248-
249-
| Error | Cause |
250-
|-------|-------|
251-
| `InvalidArgument` | More than 1,000 wallet addresses in a single request. |
252182

253-
## Batching
183+
## Errors
254184

255-
Each request accepts up to 1,000 addresses. For larger audiences, split into chunks.
185+
Both endpoints return the following errors:
256186

257-
```typescript
258-
const BATCH_SIZE = 1000;
187+
| Status | Code | Cause |
188+
|--------|------|-------|
189+
| 400 | `Bad Request` | Possible causes:<ul><li>`app_url` is missing</li><li>`title` is missing or exceeds 30 characters</li><li>`message` is missing or exceeds 200 characters</li><li>`wallet_addresses` is missing or exceeds 1,000 addresses</li><li>`target_path` exceeds 500 characters or does not start with `/`</li></ul> |
190+
| 401 | `Unauthorized` | Missing or invalid API key. |
191+
| 403 | `Forbidden` | The `app_url` does not belong to your project, or your project is not whitelisted for notifications. |
192+
| 404 | `Not Found` | The project associated with your API key does not exist. |
193+
| 503 | `Service Unavailable` | The notification service is temporarily unavailable. Retry the request. Send endpoint only. |
259194

260-
async function notifyAllUsers(allAddresses: string[], title: string, message: string, targetPath: string) {
261-
for (let i = 0; i < allAddresses.length; i += BATCH_SIZE) {
262-
const batch = allAddresses.slice(i, i + BATCH_SIZE);
263-
await fetch("https://www.base.dev/v1/notifications/send", {
264-
method: "POST",
265-
headers: { "x-api-key": process.env.BASE_DEV_API_KEY!, "Content-Type": "application/json" },
266-
body: JSON.stringify({ app_url: "https://your-app.com", wallet_addresses: batch, title, message, target_path: targetPath }),
267-
});
268-
}
269-
}
270-
```
195+
## Batching and deduplication
271196

272-
## Rate limits
197+
Each request accepts up to 1,000 addresses. For larger audiences, split your address list across multiple requests.
273198

274-
| Constraint | Limit |
275-
|------------|-------|
276-
| Requests per minute (per API key) | 10 |
277-
| Wallet addresses per request | 1,000 |
278-
| Max users notifiable per minute | 10,000 |
199+
Duplicate addresses within a single request are deduplicated automatically. Identical notifications — same app URL, wallet address, title, message, and target path — sent within a 24-hour window are also deduplicated and return a success response without sending a duplicate push.

0 commit comments

Comments
 (0)