Developers
StreetPOP API & webhooks
Pull campaigns, GPS-stamped photos and videos, and your team into your own dashboards, reporting tools or client portals. Get a webhook the moment a photo lands. Included in every plan, no add-on fee.
Getting started
- In StreetPOP, open Settings → API & webhooks and create an API key. It’s shown once, so store it somewhere safe.
- Send it on every request as
Authorization: Bearer sp_live_…. - Everything is JSON over HTTPS at
https://streetpop.app/api/v1. Read-only for now.
curl https://streetpop.app/api/v1/photos?since=2026-10-01T00:00:00Z \ -H "Authorization: Bearer sp_live_YOUR_KEY"
Endpoints
| Method | Path | What it returns |
|---|---|---|
GET | /api/v1/campaigns | Your campaigns, newest first. ?status=active (default), archived or all. |
GET | /api/v1/campaigns/{id} | One campaign: name, job #, client, dates, team, photo count, share link. |
GET | /api/v1/campaigns/{id}/photos | That campaign’s photos and videos. Same paging as /photos. |
GET | /api/v1/photos | Every photo and video, in upload order. ?since=<ISO time>&limit=1–500&campaign=<id>. |
GET | /api/v1/team | Your street team: name, email, role, photo count, markets, last photo. |
Errors come back as {"error": "…"} with a 4xx status: 401 for a missing or revoked key, 404 for an unknown campaign, 400 for a bad parameter.
Photos and videos
Each photo or video looks like this. Times are UTC. url is the full-resolution original.
{
"id": "f_3c1e9a…",
"campaign_id": "6aba9c75…",
"type": "photo", // or "video"
"source": "streetpop", // or "simplecrew" (synced from SimpleCrew)
"taken_at": "2026-09-28T22:29:14.000Z",
"uploaded_at": "2026-09-28T22:29:40.120Z",
"lat": 34.0251, "lng": -118.4512,
"address": "1600 Wilkins Ave, Los Angeles, CA",
"caption": null,
"team_member": "Chris P.",
"thumbnail_url": "https://media.streetpop.app/…-400.jpg",
"url": "https://media.streetpop.app/….jpg", // full resolution
"duration_s": null
}Staying in sync
Photos come back oldest-first by upload time. Store the next_since from each response and pass it as ?since= next time. While has_more is true, keep paging.
{
"data": [ …up to "limit" photos… ],
"has_more": true,
"next_since": "2026-10-01T18:02:11.000Z" // pass as ?since= for the next page
}Webhooks
Add a webhook URL in Settings → API & webhooks and StreetPOP will POST to it each time a new photo or video comes in (photo.created): shot in the StreetPOP app, or synced from SimpleCrew if you still use both. Synced photos usually arrive within 15 minutes. Use Send test there to check your endpoint. Answer with any 2xx status within 8 seconds.
POST https://your-system.com/streetpop-webhook
Content-Type: application/json
X-StreetPOP-Signature: sha256=5d41402abc4b2a76…
{
"event": "photo.created",
"created_at": "2026-10-04T16:20:05.311Z",
"data": { …the photo, same shape as the API… }
}Checking the signature
Every request carries X-StreetPOP-Signature: an HMAC-SHA256 of the raw request body, made with your webhook’s signing secret. Check it before trusting the data.
// Node.js: check a webhook really came from StreetPOP
import { createHmac, timingSafeEqual } from 'node:crypto';
function fromStreetPOP(rawBody, signatureHeader, secret) {
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
return signatureHeader?.length === expected.length &&
timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
}Moving from SimpleCrew?
StreetPOP can import your SimpleCrew campaigns, every photo at full resolution with its GPS location, time and photographer, plus your team. In StreetPOP, open Settings → Import from SimpleCrew, paste your SimpleCrew API key and the links to your campaigns, and you’ll see exactly what comes over before anything is copied.
Run your next campaign on StreetPOP
We’re onboarding a small group of agencies, promoters and install companies now. The first 20 companies get their first year for $5.
1918of 20 early-access spots left