ElevCert API
Push Elevation Certificate data in from your own field-collection software. Mint a bearer token in Settings › API tokens, then POST partial or completed drafts to /api/v1/certificates.
Authentication
Every request must carry a bearer token in the Authorization header. Tokens are minted per user (the firm’s account) and authenticate as that user. Two firms on the same ElevCerttenant never see each other’s rows.
Authorization: Bearer <TOKEN>
- Missing header →
401 unauthorized. - Unknown / revoked token →
401 unauthorized(no existence leak). - More than 60 requests / minute for one token →
429 rate_limitedwithRetry-Afterseconds.
Idempotency
Every write accepts an optional Idempotency-Key header. Replays with the same key target the same draft row, so a retry on a flaky connection never duplicates the certificate. Omit the header and the server assigns a fresh id.
Idempotency-Key: job-2026-001
Endpoints
POST /api/v1/certificates— create or update a draft byIdempotency-Key. Returns201on first write,200on update.GET /api/v1/certificates?limit=10&cursor=<id>— list the caller’s drafts, newest first.limitis clamped to [1, 50].GET /api/v1/certificates/{id}— fetch one row the token user owns.404on wrong owner.PATCH /api/v1/certificates/{id}— update an existing draft. Same shape asPOST.DELETE /api/v1/certificates/{id}— remove a draft.204on success,404on wrong owner.
Request shape
Every write carries a single state blob. If step is done, the server runs the master Certificateschema (which enforces cross-section refinements such as “lowest floor elevation required for AE/VE/AO/AH zones”). Otherwise it accepts the partial Wizard state(sections may be absent until filled).
The signatureMethod enum is now ["upload"]— a typed name is not a valid professional seal in most jurisdictions. ThesignatureValue must be a data:image/png or data:image/jpeg URL. A sign attempt also requires a certifier seal image to be on file for the user (uploaded in /profile) — without it the sign route returns 400 with { "error": "seal_required", "sealUploadUrl": "/profile#certifier-seal" }.
{
"step": "A",
"state": {
"sectionA": {
"result": {
"panel": "0308",
"suffix": "F",
"effectiveDate": "2024-09-12",
"zone": "AE",
"bfe": 9.0,
"community": "City of Cedar Bay",
"county": "Cedar County",
"communityId": "485012",
"datum": "NAVD88",
"sourceUrl": "https://hazards.fema.gov",
"lookupLatencyMs": 120,
"warnings": [],
"stale": false
},
"address": { "street": "1428 Baycrest Dr", "city": "Cedar Bay", "state": "TX", "zip": "77566" },
"acks": { "datumAcknowledged": true, "staleAcknowledged": true }
}
},
"clientReferenceId": "job-2026-001"
}Validation failures return 400 with { "errors": { "sectionA.address.zip": "Invalid" } }. Cross-section gates (for example Section G required when the building is in a coastal A-zone) fold into the envelope’s certificate key.
Response shape
Each row returns exactly the fields the integrator needs to round-trip a job ID and pick out the draft’s library status.
{
"id": "ckxyz…",
"step": "A",
"state": { /* WizardState or Certificate */ },
"updatedAt": "2026-08-06T15:00:00.000Z",
"formVersion": "FF-206-FY-22-152",
"paidAt": null,
"hasSignature": false,
"clientReferenceId": "job-2026-001"
}formVersionis stamped at draft creation from the published FEMA FF-206-FY-22 form version.nulluntil the daily form-version cron runs.paidAtis the verification timestamp of the most recent paid Order against this draft.hasSignaturetells the integrator whether a signing step has already applied against the row — useful when resuming after a pause.
Curl examples
Step A: create a new draft. The server returns 201with the row’s assigned id; capture it or replay the same Idempotency-Key.
curl -X POST https://plumbseal.polsia.app/api/v1/certificates \
-H "Authorization: Bearer <TOKEN>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: job-2026-001" \
-d '{
"step": "A",
"state": {
"sectionA": {
"result": {
"panel": "0308",
"suffix": "F",
"effectiveDate": "2024-09-12",
"zone": "AE",
"bfe": 9.0,
"community": "City of Cedar Bay",
"county": "Cedar County",
"communityId": "485012",
"datum": "NAVD88",
"sourceUrl": "https://hazards.fema.gov",
"lookupLatencyMs": 120,
"warnings": [],
"stale": false
},
"address": { "street": "1428 Baycrest Dr", "city": "Cedar Bay", "state": "TX", "zip": "77566" },
"acks": { "datumAcknowledged": true, "staleAcknowledged": true }
}
},
"clientReferenceId": "job-2026-001"
}'Step “done”: complete the certificate. The master schema enforces every cross-section gate, so a 400 here means a real validation gap, not a transport issue.
curl -X PATCH https://plumbseal.polsia.app/api/v1/certificates/JOB_ID \
-H "Authorization: Bearer <TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"step": "done",
"state": {
"sectionA": { /* … */ },
"sectionB": { "firmMapAction": "none", "coastalAVZone": false, "floodZoneBaseSource": "NFHL" },
"sectionC": {
"diagram": "1A",
"buildingUse": "residential",
"squareFootage": 2400,
"highestNextHigherFloorElevation": 12.5,
"lowestFloorElevation": 10.0,
"baseFloodElevation": 9.0
},
"sectionD": { "rows": [] },
"sectionH": {
"licenseNumber": "LS-0042118",
"licenseJurisdiction": "TX",
"certifierName": "Jane Doe",
"certifierTitle": "LicensedLandSurveyor",
"signatureMethod": "upload",
"signatureValue": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
}
}
}'Operations notes
- This API writes drafts only; signing and PDF export stay on the cookie-authed web flow (a firm’s pushed drafts are still reviewable and signable in /library).
- Tokens never expire on their own. Revoking a token invalidates it immediately, and a follow-up request returns 401.