Server API reference
Every call the app makes to the Civiq backend — 43 in all — with its verb, the code that calls it, and which auth layer it rides on.
Reading the auth column: device means the request carries device-attestation headers (per-request App Attest assertions on iOS, the session token on Android — see Attestation); device + bearer adds the user's Authorization: Bearer token on top; none marks the two endpoints that mint attestation and therefore cannot require it. The transport applies one automatic retry after recovery on 403 (attestation) and 401 (token refresh) for every call below.
Device attestation
| Path | Caller | Auth |
| GET | /device/challenge | fetchDeviceChallenge — ceremony on both platforms, plus every iOS signed request | none |
| POST | /device/attest | DeviceCheckService / AndroidIntegrityService._verifyAttestation | none |
Congressional & state data
| Path | Caller | Auth |
| GET | /representatives/congress/status | RepresentativesService._getCongressStatus — five dataset SHAs | device |
| GET | /representatives/congress/data | legislators sync (file-cached) | device |
| GET | /representatives/congress/offices | offices sync (file-cached) | device |
| GET | /representatives/congress/socials | socials sync (file-cached) | device |
| GET | /representatives/congress/committees | committees sync (file-cached) | device |
| GET | /representatives/congress/committee-memberships | memberships sync (file-cached; JSON object) | device |
| GET | /representatives/states/status?states=…&districts=all | _getPeopleStateStatus — per-state SHA | device |
| GET | /representatives/states?states=…&districts=… | _insertPeopleStateData — state legislator slice | device |
| GET | /districts/{STATE} | DistrictBoundaryService._load — GeoJSON, ETag-cached | device |
| POST | /geocodio/district | GeocodioService.getGeocodioInfo — district resolution | device |
Bills & images
| Path | Caller | Auth |
| POST | /representatives/bills/highlight | _queryBillsHighlights — {representative_id} | device |
| POST | /representatives/images | fetchRepresentativeImages — batch ≤ 100 uuids | device |
| GET | /representatives/images/{uuid} | ImageUtil — per-rep fallback | device |
| POST | /representatives/photo-hint | ImageUtil — Wikipedia-title signal, fire-and-forget | device |
Contact delivery & AI
| Path | Caller | Auth |
| POST | /representatives/contact | ContactService.sendMessage | device + bearer |
| GET | /representatives/contact/{message_id} | ContactService.getMessage — 3 s poll | device + bearer |
| GET | /representatives/contact | ContactService.listMessages — no UI caller yet | device + bearer |
| POST | /ai-email | AiService.generateEmail — proxied DeepSeek, non-streaming | device |
Account
| Path | Caller | Auth |
| POST | /account/signup | signUpWithEmail | device |
| POST | /account/signin | signInWithEmail | device |
| POST | /account/oauth/google | signInWithGoogle | device |
| POST | /account/oauth/apple | signInWithApple | device |
| POST | /account/refresh | refreshTokens — rotates the pair | device |
| POST | /account/signout | signOut — failure ignored, local state cleared | device + bearer |
| GET | /account/profile | fetchProfile | device + bearer |
| PUT | /account/profile | updateProfile — name / ZIP | device + bearer |
| POST | /account/photo | uploadPhoto — raw JPEG bytes | device + bearer |
| GET | /account/photo | loadPhoto | device + bearer |
| DELETE | /account/photo | deletePhoto | device + bearer |
| DELETE | /account | deleteAccount — server must succeed first | device + bearer |
Community
| Path | Caller | Auth |
| GET | /community/hubs | loadHubs | device + bearer |
| GET | /community/posts?hub=…&cursor=… | loadPosts — cursor pagination | device + bearer |
| POST | /community/posts | createPost — optional bill/rep anchor | device + bearer |
| DELETE | /community/posts/{post_id} | deletePost — author only | device + bearer |
| GET | /community/posts/{post_id}/comments | loadComments — flat list, depth field | device + bearer |
| POST | /community/posts/{post_id}/comments | addComment | device + bearer |
| DELETE | /community/comments/{comment_id} | deleteComment — author only | device + bearer |
| PUT | /community/likes | setPostLike / setCommentLike — optimistic | device + bearer |
| POST | /community/reports | report | device + bearer |
Reporting
| Path | Caller | Auth |
| POST | /reports/bug | submitManualBugReport — 202 Accepted | device |
| POST | /reports/crash | submitPendingCrashReport — consent-gated | device |
Adjacent traffic not in the count
Map tiles are fetched from the server's own proxy at /tiles/{z}/{x}/{y} by the map widget's tile layer (keeping the MapTiler key server-side), with a keyless CartoCDN basemap as fallback after five consecutive tile errors. Three third-party hosts are reached directly — the unitedstates.github.io congress images, the Wikipedia pageimages API, and CartoCDN tiles — always through a separate HTTP client so attestation and account headers never leave the Civiq API, HTTPS-only, with a 10-second timeout.
Transport behavior common to all calls: no client-side timeouts on Civiq API calls (only third-party image fetches have one); success on any 2xx for writes (202 for reports, 204 for likes); one automatic retry after 401/403 recovery; requests serialized per path+query through 256 striped locks; no multipart anywhere — the single binary upload (profile photo) posts raw bytes.