Daily Login Streaks

A daily claim ladder that rewards consecutive visits — day 1 pays small, day 7 pays big. One endpoint. Pulse tracks the calendar, the grace rules, and the ladder position; you just call claim when the player shows up.

Claim

POST /v1/streaks/{streak_id}/claim

{"player_id": "your-player-ref", "idempotency_key": "uuid"}

-> {"status": "claimed",
    "streak": 4,                       // consecutive days, 1-based
    "rung": 4,                         // ladder position paying out
    "currency": "SC", "amount": 500,
    "grant": { "grant_id": "...", "signature": "...", ... }}

Call it whenever the player logs in or opens their rewards page — the platform sorts out the rest:

Ladder amounts, timezone, and grace policy are configured with the Pulse team at onboarding — your integration is just the claim call.

Rendering a streak calendar

The claim response carries everything a "day 4 of 7" UI needs: current streak, the rung that just paid, and on repeat calls the next_claim_date. Most sites render a 7-day strip with checkmarks up to rung and the upcoming rewards greyed out.

Collecting the reward

Paying claims append a signed grant to your outbox — verify, credit, ack, same as every feature. The response includes the grant inline for immediate UX.

Endpoint summary

EndpointPurposeBillable
POST /v1/streaks/{id}/claimclaim today's rungyes (1 token; same-day repeats free)

Integrate with your coding agent

Building with Claude, Cursor, Copilot, or another AI agent? Paste this prompt — it contains the complete contract, the security rules, and an acceptance checklist, so your agent can implement the integration without reading these docs.

Integrate the Pulse Rewards "Daily Login Streaks" feature into this
codebase. Pulse is a gamification API for sweepstakes sites. Follow this
architecture exactly - it is a hard security contract.

## Non-negotiable rules
1. The Pulse API key (env PULSE_API_KEY, "prw_...") is backend-only:
   never in frontend bundles or browser requests.
2. All Pulse calls go frontend -> our backend -> Pulse.
3. Player balances live in OUR wallet. Pulse pays via signed grants we
   verify, apply idempotently, and acknowledge.
4. Every claim sends an idempotency_key (UUID per user action).

## Pulse API (base https://api.playwithpulse.com, header
## "Authorization: Bearer <PULSE_API_KEY>")
- POST /v1/streaks/{streak_id}/claim
    body {"player_id": str, "idempotency_key": str}
    Success -> {"status": "claimed", "streak": int, "rung": int,
                "currency": str, "amount": int, "grant": {...}?}
    Same local day -> {"status": "already_claimed", "streak": int,
                       "next_claim_date": "YYYY-MM-DD"} (an answer, not
    an error, and not billed). The day boundary uses the timezone
    configured for our site on Pulse's side; do NOT compute any streak
    or calendar logic locally - render what the API returns. Amounts
    are integers in our smallest currency unit (500 = 5.00 SC).
- GET /v1/outbox and POST /v1/outbox/{grant_id}/ack for settlement
    (shared with all Pulse features; reuse the outbox worker if one
    already exists in this codebase).

## Build these pieces
A. Backend endpoint POST /api/pulse/streak/claim (player must be
   authenticated): mint a UUID idempotency key, call Pulse claim with
   the player's stable id, return {status, streak, rung, amount_text,
   next_claim_date?} where amount_text formats amount/100 with the
   currency label.
B. Call it automatically once on login/session start AND expose it to a
   "claim today's reward" button. Handle both statuses: "claimed" shows
   a reward toast; "already_claimed" quietly updates the calendar UI.
C. A streak calendar UI: a 7-slot strip using the response - checkmarks
   for days 1..rung, today highlighted, remaining rungs greyed. (If the
   configured ladder is longer/shorter ask me; do not hardcode rewards -
   show amounts only for the day just claimed.)
D. Outbox settlement: identical to the Pulse wheel integration. If this
   codebase already has the Pulse outbox worker, reuse it unchanged -
   streak grants flow through the same pipe. Otherwise implement:
   poll GET /v1/outbox; verify HMAC-SHA256(key=PULSE_WEBHOOK_SECRET,
   msg=canonical JSON of the grant minus grant_id and signature, keys
   sorted, separators ("," ":")) against grant["signature"] in constant
   time; apply to the wallet idempotently by grant_id; then ack.

## Acceptance checklist
- [ ] Claiming twice in one (site-timezone) day: first returns
      "claimed", second "already_claimed"; exactly one wallet credit.
- [ ] Replaying the same idempotency key returns the identical body.
- [ ] The calendar renders purely from API responses (no local date
      math beyond display formatting).
- [ ] Tampered grant signatures are rejected, logged, never acked.
- [ ] PULSE_API_KEY appears nowhere in built frontend assets.
Ask me for: PULSE_API_KEY, PULSE_WEBHOOK_SECRET, our streak_id, the
player id mapping, and the wallet credit function/location.