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.

Get early access

Getting started

  1. In StreetPOP, open Settings → API & webhooks and create an API key. It’s shown once, so store it somewhere safe.
  2. Send it on every request as Authorization: Bearer sp_live_….
  3. 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

MethodPathWhat it returns
GET/api/v1/campaignsYour 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}/photosThat campaign’s photos and videos. Same paging as /photos.
GET/api/v1/photosEvery photo and video, in upload order. ?since=<ISO time>&limit=1–500&campaign=<id>.
GET/api/v1/teamYour 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

Get early access