# SibFly
> Satellite-measured ground motion (sinking and uplift) for any US address, in mm/year AND in/year, from NASA OPERA DISP-S1 (Sentinel-1 InSAR).
## Endpoints
- GET /api/v1/motion?address=
(or ?lat=&lon=) -> one report. Costs $0.40 from prepaid credits.
optional: &include=timeseries (embed full history in the same call), &brief=1 (plain-English narration).
- POST /api/v1/motion/batch body {"items":[{"id":"a","address":"..."},{"id":"b","lat":..,"lon":..}]} -> array; up to 1000; ONLY covered rows are billed.
- GET /api/v1/balance -> credits_usd, price_per_report_usd, reports_remaining_est, top_up_url. Free.
- GET /api/v1/timeseries , /api/v1/map , /api/v1/coverage (coverage is free).
- No key -> free teaser with coverage, confidence, and would_cost_usd (a free 'should I buy?' check).
## Response fields (every value carries its unit in the key name)
- velocity_vertical_mm_yr + velocity_vertical_in_yr, velocity_uncertainty_mm_yr + _in_yr, seasonal_amplitude_mm + _in,
total_motion_mm + _in, display_units, trend, assessment, confidence, frame, n_measurements, span_years,
last_observation, data_age_days, data_coverage, provenance{calibration,...}, pixel_polygon, cell_size_m.
- Billing on every paid response: cost_usd, credits_remaining_usd, request_id (also headers X-Request-Id, X-Credits-Remaining).
## Agent notes
- Auth: header 'Authorization: Bearer '.
- Idempotency: send header 'Idempotency-Key: '; a retry with the same key replays the cached result and is NOT charged again (7 days). Works on motion and batch.
- Misses are FREE: out-of-coverage / no-data / bad address return HTTP 200 with coverage:false and cost_usd:0 (never 404, never billed).
- Out of credits: HTTP 402 with top_up_url + suggested_top_up_usd so you can self-refill. Errors are JSON {error,code,message,retryable,doc_url,request_id}.
- Rate limit (un-keyed only): HTTP 429 with Retry-After. Bearer keys are not rate-limited.
## Get a key (free, no card, no phone, no name)
- POST https://sibfly.com/signup with form fields email, password, agree=1. Your API key is shown on /dashboard. First report free.
## Docs
- Interactive: https://sibfly.com/api/docs OpenAPI: https://sibfly.com/openapi.json Index: https://sibfly.com/api/v1
- US coverage, expanding. Information only, not a survey or guarantee. Terms: https://sibfly.com/terms
## Query params on GET /api/v1/motion
- dry_run=1 -> FREE 'should I buy?' oracle on a covered point: coverage, confidence, data_age_days, would_cost_usd (never the rate, never billed).
- max_age_days=N -> if the best data is older than N days, returns status:stale_data at $0 (not billed) instead of a stale reading.
- min_confidence=X (0-1) -> if pixel confidence < X, returns status:low_confidence at $0 (not billed).
- include=timeseries, brief=1 (see below).
## assessment_code (stable enum; thresholds on vertical mm/yr)
- <=-8 rapid_subsidence | (-8,-3] notable_subsidence | (-3,3) stable | [3,8) mild_uplift | >=8 strong_uplift. Route logic on assessment_code, not the human 'assessment' string.
## More response fields
- pixel_id (stable 30 m cell id), seasonal_dominant (bool: seasonal swing > trend; read with care), velocity_display ('-6 mm/yr (-0.24 in/yr) +/-2'), queried_at (UTC), reports_remaining, query.geocoded_address (resolved canonical address).
- Misses carry no_data_reason / no_coverage_reason. ETag + Cache-Control on /motion: send If-None-Match to revalidate for free (304, no charge).
## brief=1 narration
- Adds ground_brief (plain-English) + engine code. Free accounts get GL52 (GLM), paying accounts get CL46 (Opus). It always restates the number, the +/- uncertainty, the data date, and the screening disclaimer; it does not invent numbers or give buy/sell/safety advice. Off the fast path (opt-in).
## Batch response shape (POST /api/v1/motion/batch)
- {status, summary:{requested,billed,succeeded,no_coverage,invalid,total_cost_usd}, credits_remaining_usd, request_id, results:[ per item ]}.
- Each result: {id, status:'ok'|'no_data'|'no_coverage'|'invalid_address'|..., cost_usd, ...motion fields when ok}. Duplicate cells in one batch are charged once and flagged same_pixel_as. Send header Idempotency-Key to replay the whole batch for free.
## Other endpoints
- GET /api/v1/coverage?address=|lat=&lon= -> per-point {covered, frame, data_age_days, would_cost_usd} (FREE). No args -> live frame list.
- GET /api/v1/timeseries?...&reference_date=YYYY-MM-DD&reference_date2=YYYY-MM-DD -> adds delta{movement_mm,movement_in,uncertainty_mm,days_between}: how much it moved between two dates.
- POST /api/v1/account/spend_cap {"daily_usd": N} -> per-key DAILY spend cap (402 spend_cap_reached before drain); null removes it.
- POST /api/v1/coverage/batch -> FREE batch coverage pre-check (covered/age/would_cost per row); size a job before spending.
- GET /api/v1/frames and /api/v1/frames/{id}/last_updated -> when frames last got new data (poll to re-pull). FREE.
- POST /api/v1/motion/batch with {"async":true} -> 202 {job_id}; poll GET /api/v1/motion/batch/{job_id}. {"since":"YYYY-MM-DD"} -> diff-billing (only newly-updated rows billed).
## MCP server
- POST https://sibfly.com/mcp (Streamable HTTP, JSON-RPC). Auth: Bearer . Tools: check_ground_motion, check_portfolio, get_coverage, get_motion_history, get_account. Misses/coverage/account are free; reports cost $0.40.
## Agent onboarding & keys (no human needed)
- POST /api/v1/autonomous/register {email, password?} -> {api_key, free credits}. One call, no captcha; if password omitted it's generated and returned.
- POST /api/v1/keys {daily_cap_usd?, allowed_referer?, ttl_hours?, label?} -> a SCOPED child key (draws from your credits, capped + referer-locked + revocable). Safe to embed in a public browser widget. GET /api/v1/keys lists; DELETE /api/v1/keys/{key} revokes.
## More motion params
- min_geocode_score=0..1 -> free miss if the address only matched a coarse centroid (query.geocode gives match_type/score). compact=1 / fields=a,b,c / units=mm|in -> control payload size.
- Response adds: near_threshold + significant_nonzero (is the label trustworthy / distinguishable from zero), neighbor_consistent (False = lone-pixel artifact risk), provenance.license (caching/re-serve grant).
## Machine-readable schema
- GET /api/v1/schema -> all enums (assessment_code, status, freshness, error_codes, batch/job statuses, mcp_arg_map), thresholds, free gates, and the decision-tree as JSON. Parse this instead of prose.
## Free helper & account endpoints (no charge)
- GET /api/v1/geocode?address= -> {lat, lon, matched_address, match_type, score}. Resolve an address to a point without buying a report.
- GET /api/v1/pixel?lat=&lon= -> the exact 30 m cell polygon (GeoJSON) that covers a point.
- GET /api/v1/usage?limit=N -> your line-item ledger (each charge: ts, endpoint, q, cost_usd).
- GET /api/v1/me -> your account (email, credits_usd, plan, joined). GET /api/v1/my-reports -> reports you've bought. GET /api/v1/my-chats -> your inspector chats.
- POST /api/v1/chat {report_id|address, message} -> AI inspector Q&A about a specific report (uses the report you already bought; narration cost, not a new report charge).
- POST /api/v1/support {email, message} / GET /api/v1/support -> open + read support tickets.
## Paid artifacts
- GET /report.pdf?address= (&brief=1 for narration, &label= for a display name) -> a rendered PDF report. Same $0.40 as a motion report (idempotent per point/day on the web tier).
- GET /api/v1/map?address=&half_km= -> a velocity heatmap PNG around the point ($0.40; un-keyed = a free low-res teaser).
## MCP tool arg names differ from REST params (see /api/v1/schema mcp_arg_map)
- check_ground_motion: use 'explain' (not brief). get_motion_history: use 'from_date'/'to_date' (map to reference_date/reference_date2). get_account -> GET /api/v1/balance. check_portfolio takes addresses[] only (no per-item gates). Over MCP, units/fields/include/mock are not exposed — use the REST API for those.