# 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). - /timeseries series_type: 'measured' = real per-date satellite passes (PREMIUM frames with a 150 m history cube; cell_m included) ; 'modeled' = the fitted trend evaluated on real acquisition dates (standard frames, see series_note; delta uncertainty = velocity sigma x years). - 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) on /motion, /timeseries, /map. (The /api/v1/geocode helper alone 404s on address_not_found.) - An invalid/unknown Bearer key returns HTTP 401 invalid_api_key (never a silent teaser) — re-check your key or register a new one. - Idempotency also works on GET /api/v1/timeseries (same 7-day replay). - Out of credits: HTTP 402 with top_up_url + suggested_top_up_usd + a buy_api block so you can self-refill (see 'Top up by API'). Errors are JSON {error,code,message,retryable,doc_url,request_id}. - Rate limits: un-keyed 30/min/IP; Bearer keys a generous 120/min/key. 429s carry Retry-After; keyed 200s carry RateLimit-Limit/-Remaining/-Policy so you can pace BEFORE the wall. Bulk work -> use batch. ## Get a key (free, no card, no phone, no name, no captcha) - Agents: POST https://sibfly.com/api/v1/autonomous/register {"email":"you@example.com"} -> {api_key, $1.00 free credits = 2 reports at $0.40, password}. One call, instant. (Anti-abuse: 5 registrations/day/IP; free credits on the first 2.) - TEST MODE: add {"mode":"test"} -> an sf_test_ key with $100 FAKE credits and SYNTHETIC data; /api/v1/buy auto-credits instantly. Rehearse the full register->check->402->buy->retry loop for $0. - Humans: POST https://sibfly.com/signup with form fields email, password, agree=1; key shown on /dashboard. ## Top up by API (no browser) - POST /api/v1/buy {"amount_usd": 10} (Bearer) -> {checkout_url} (Stripe card session). {"method":"crypto"} -> {invoice_url, txn_id} (BTC). - Open/relay the URL to complete payment; credits land automatically (webhook); poll GET /api/v1/balance to confirm. Every 402 body repeats these calls in its buy_api block. - x402 (fully in-band, no links, no signup): GET or POST /api/v1/x402/motion?address=... -> HTTP 402 with the x402 v2 challenge (PAYMENT-REQUIRED header + accepts[] USDC on Base, CAIP-2 eip155:8453). Sign the transfer and RETRY with the PAYMENT-SIGNATURE header -> we verify+settle via the public Coinbase facilitator and serve the report in that same response (receipt in PAYMENT-RESPONSE). Discovery: /.well-known/x402. (Bearer-key /api/v1/motion 402s also carry accepts[] for keyed callers.) - OAuth 2.1 for connector installs: /.well-known/oauth-authorization-server (DCR at /oauth/register, PKCE S256). Headless agents should keep using raw Bearer keys. ## 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, PLUS answer-quality prediction: expected_uncertainty_mm_yr + likely_significant (false = a paid report would say no_clear_trend). Never the rate, never billed. - min_significance=1 -> if the rate is within its error bar (would be no_clear_trend), returns status:not_significant at $0 — never pay for statistical noise. - 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). NOTE: confidence measures pixel DATA quality (coherence x record length), NOT trend certainty — use min_significance / likely_significant for that. - include=timeseries, brief=1 (see below). ## confidence_tier (READ THIS to route trust) — graded signal-to-noise, not a binary - confidence_tier = one of: 'measured' | 'likely' | 'within_margin', from signal_to_noise = |velocity| / velocity_uncertainty_mm_yr. - 'measured' (SNR>=2): the direction is confident — trust the sign + magnitude. - 'likely' (1<=SNR<2): leans a direction but within one margin — suggestive, NOT conclusive. Weight accordingly. - 'within_margin' (SNR<1): the rate is inside its error bar. Split by within_margin_kind (below). - within_margin_kind (present only when confidence_tier=='within_margin'): 'stable' = a CONFIDENT no-significant-movement PASS (a real green-flag answer, BILLED $0.40); 'inconclusive' = the radar can't give a trustworthy rate here (low coherence / stale) = a non-answer, returned as status:'inconclusive' with cost_usd:0 (NEVER billed, on motion, batch, and PDF). - differential_motion (optional block): present when the ground moves unevenly over ~150 m (neighbor_spread>3 mm/yr). The foundation-relevant signal — uneven motion cracks buildings, uniform motion usually doesn't. - neighborhood (block on every covered hit): block-vs-block differential at ~300 m — median of the ~210 m block around your address vs 8 compass blocks. Fields: max_block_difference_mm_yr, strongest_direction, neighbor_faster_sinking, pattern ('uniform' = whole area moves together, benign | 'mild_gradient' | 'strong_gradient' = edge-of-subsidence-bowl, the stress pattern that merits an elevation survey). Calibration-independent (same-frame differences cancel the offset). CAVEAT: when pattern=='uniform', strongest_direction is the argmax of 8 noise-level differences — a statistically meaningless compass point; ignore it unless pattern is a gradient. - Honest uncertainty: velocity_uncertainty_mm_yr is floored to each frame's held-out GPS RMSE (~4-6 on stable ground; a FLOOR, typical parcels may be worse), or 4.0 on uncalibrated frames. It is ABSOLUTE accuracy vs GPS, wider than a naive fit error. dry_run predicts the tier + whether a paid report will bill. ## 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 DIRECTION on assessment_code, but gate TRUST on confidence_tier (above). - no_clear_trend / 'stable' code appears when SNR<1 (within margin); see confidence_tier for whether that's a confident 'stable' PASS or an 'inconclusive' free non-answer. ## 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. Free gates: dry_run=1 (points+cost preview), max_age_days=N. max_points=N decimates the series (token diet, keeps first+last). Returns cost_usd + credits_remaining_usd; honors Idempotency-Key (replays report cost_usd:0 + original_cost_usd). - 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/webhooks {"url":"https://...","events":["frame.updated","balance.low","batch.done"]} -> push instead of polling. Standard-Webhooks HMAC signing (headers webhook-id/-timestamp/-signature; verify HMAC-SHA256(secret, id+'.'+ts+'.'+body)). GET lists, DELETE /api/v1/webhooks/{id} removes. Max 5/account. - GET /status -> health + per-frame freshness lag. GET /changelog.json -> additive API changelog + versioning policy. - 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 (max_spend_usd budget cap), get_coverage (accepts addresses[] for free batch sizing), get_motion_history, get_account, get_usage (billing ledger), ask_inspector (Q&A about a bought report), register_agent (get a key, FREE, no auth needed), buy_credits (payment link). The FULL lifecycle — register -> check -> understand -> pay -> top up -> reconcile — works inside MCP. 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. (404 address_not_found if neither Census nor the OSM fallback matches.) - 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 carries ts, endpoint, address, cost_usd AND request_id (matches the report's request_id -> full reconciliation). - GET /api/v1/me -> your account {email, credits_usd, plan, joined, total_reports, last30, spent30_usd}. GET /api/v1/my-reports -> reports you've bought. GET /api/v1/my-chats -> your inspector chats. - POST /api/v1/chat {messages:[{role,content},...], context: , convo?} -> {reply}. AI inspector Q&A about a report (no new report charge; per-account allowance -> 429 chat_quota_exhausted when spent). Agent shortcut: pass {address:"..."} instead of context and the server loads your bought report for that address. - POST /api/v1/support {message, subject?, priority?} / GET /api/v1/support -> open + read support tickets (subject derived from message if omitted). ## 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. ## Official SDKs (zero-dependency, retries + auto Idempotency-Key + typed InsufficientCredits/AuthError) - Python: pip install sibfly -> from sibfly import SibFly; SibFly.register('you@example.com').motion(address='...') - Node (>=18): npm install sibfly -> const {SibFly} = require('sibfly') ## Framework integrations (same API, drop-in tools) - LangChain: pip install langchain-sibfly (SibflyGroundMotion tool) - CrewAI: pip install crewai-sibfly (SibflyGroundMotionTool) - LlamaIndex: pip install llama-index-tools-sibfly (SibflyToolSpec) - n8n: n8n-nodes-sibfly (community node) - MCP (any client): https://sibfly.com/mcp ## Human/SEO pages (measured data, good for citations) - https://sibfly.com/how-fast-is-america-sinking -> the fastest-measured US subsidence, ranked, with a Dataset. - https://sibfly.com/sinking -> ground motion for every covered US metro; /sinking/ and /sinking/ for detail.