graphai checker

check-wireguard.graphai.eu

Checker Apps Sign in Sign up

check-wireguard.graphai.eu · HTTP API

REST documentation

Marketplace downloads, Stripe, Algorand x402, live probes, and ClickHouse query endpoints.

Back to checker List queries Postman collection OpenAPI

REST API

Authenticated users download marketplace statistics as JSON. Unauthenticated calls get 401. Unpaid catalog downloads get 402 Payment Required with Algorand x402 accepts. Card payments use Stripe.js (STRIPE_PUBLIC_KEY) and a Checkout Session; pass session_id or Stripe-Session to download. Sign in with Clerk, then send the session JWT.

GET /api/data — catalog (aggregated)

Lists statistics other users have offered. Default payload is aggregated (n, min/median/max RTT, quality, age). Raw timestamp, IP, and GPS are present only when the seller checked non-aggregated sale.

GET /api/data
Authorization: Bearer <Clerk session JWT>   # optional for catalog; required for ?download=1

200 application/json
{
  "object": "catalog",
  "listings": [
    {
      "id": "…",
      "kind": "path-10s",
      "n": 12,
      "rtt_min_ms": 18.2,
      "rtt_median_ms": 24.1,
      "rtt_max_ms": 41.0,
      "quality": "Medium",
      "raw": false
    }
  ]
}
Try GET /api/data

GET /api/data?download=1 — download (authenticated)

Returns the listings the caller is allowed to take home. Requires a Clerk session. If the catalog is unpaid, the API answers 402 unless an Algorand payment header or a paid Stripe Checkout session_id is present.

GET /api/data?download=1
Authorization: Bearer <Clerk session JWT>
PAYMENT-SIGNATURE: <x402 payment header, if the quote required payment>
Stripe-Session: cs_…                 # optional, after Stripe.js Checkout

401 { "error": "sign in required" }
402 { "x402Version": 2, "accepts": [ { "scheme": "exact", "network": "algorand:…", … } ] }
200 { "object": "download", "listings": [ … ], "paid": { "provider": "stripe", "session": "cs_…" } }
Try GET /api/data?download=1

POST /api/data — offer rows for sale

Clerk-authenticated sellers publish the rows they ticked. Set raw: true only after the non-aggregated checkbox (timestamp, IP, GPS + Graphai OÜ liability release).

POST /api/data
Authorization: Bearer <Clerk session JWT>
Content-Type: application/json

{ "raw": false, "listings": [ { "id": "…", "kind": "path-10s", "n": 10, "rtt_median_ms": 22 } ] }

GET|POST /api/x402 — Algorand payment

x402 exact scheme on Algorand TestNet USDC (ASA 10458941). A request without PAYMENT-SIGNATURE returns HTTP 402 and a payment-required header. After the wallet pays, retry with the payment header; the facilitator verifies and settles, then /api/data?download=1 returns the JSON.

POST /api/x402
Content-Type: application/json

{ "action": "quote", "catalog": true }

402
{
  "x402Version": 2,
  "accepts": [{
    "scheme": "exact",
    "network": "algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
    "maxAmountRequired": "120000",
    "resource": "/api/data?download=1",
    "mimeType": "application/json",
    "extra": { "asset": "10458941", "decimals": 6 }
  }],
  "facilitator": "https://facilitator.goplausible.xyz"
}

POST /api/x402
PAYMENT-SIGNATURE: <base64 payment>
{ "action": "settle", "catalog": true }

200 { "ok": true, "channel": "algorand-x402", "tx": "…", "resource": "/api/data?download=1" }
Try GET /api/x402

GET|POST /api/payouts — Stripe (STRIPE_PUBLIC_KEY)

The browser loads Stripe.js and calls Stripe(STRIPE_PUBLIC_KEY). The publishable key is returned by GET (never the secret). POST creates a Checkout Session with STRIPE_SECRET_KEY; the client then uses redirectToCheckout({ sessionId }) or embedded Checkout with client_secret.

GET /api/payouts
200 {
  "object": "stripe",
  "publishableKey": "pk_live_…",
  "checkoutReady": true,
  "js": "https://js.stripe.com/v3/"
}

GET /api/payouts?session_id=cs_…
200 { "publishableKey": "pk_…", "session": { "id": "cs_…", "paid": true, "payment_status": "paid" } }

POST /api/payouts
{ "provider": "stripe", "action": "buy", "ui_mode": "embedded", "row": { "id": "catalog", "listEur": 0.12 } }

200 {
  "id": "cs_…",
  "client_secret": "cs_…_secret_…",
  "publishableKey": "pk_…",
  "ui_mode": "embedded"
}

GET /api/data?download=1&session_id=cs_…
Authorization: Bearer <Clerk session JWT>
Stripe-Session: cs_…
Try GET /api/payouts

GET /api/ip — IPv4 / IPv6 of this request

Classifies the address that reached Vercel. fam=ipv4 or fam=ipv6 sets match when the connecting family equals the request.

GET /api/ip
GET /api/ip?fam=ipv6

200 {
  "ip": "2001:db8::1",
  "family": "ipv6",
  "ipv4": null,
  "ipv6": "2001:db8::1",
  "match": true,
  "wanted": "ipv6"
}
Try GET /api/ip Try ?fam=ipv4 Try ?fam=ipv6

Live probe (no auth)

The current request path, not the marketplace: /?format=json, /api/stats, CSV variants. client.ip_family is ipv4 or ipv6.

ClickHouse analytics (no auth)

Every completed path sample, networkQuality run, HTTP/2, HTTP/3, IPv4, IPv6, and video test is stored in ClickHouse table graphai.check_wireguard_analytics (EU, europe-west2). Map and table sites share the same runs via graphai.wg_shared (POST/GET /api/share), so wireguard-map and wireguard-data load the checker dataset without relying on postMessage. Named SQL queries are exposed as HTTP GET endpoints.

List queries Download Postman collection Download OpenAPI

Postman: Import → File → pick the Postman collection JSON. Insomnia / Bruno / Hoppscotch: import the OpenAPI file. Same files at /clickhouse/postman.json and /clickhouse/openapi.json.

POST /api/share
Content-Type: application/json
credentials: include

{ "dataset": { "dwells": [ … ] }, "kinds": ["all","geo","plain"], "clerkUserId": "user_…" }

200 { "ok": true, "source": "clickhouse", "kinds": ["all","geo","plain"] }

GET /api/share?kind=geo
credentials: include
200 { "ok": true, "dataset": { "dwells": [ … ] }, "source": "clickhouse" }

POST /api/clickhouse
Content-Type: application/json

{ "kind": "path-10s", "at": "…", "snapshot": { "client": {…}, "wireguard": {…}, "browser": {…} } }

200 { "ok": true, "inserted": 1, "table": "graphai.check_wireguard_analytics" }

GET /api/queries
200 {
  "object": "query-catalog",
  "downloads": {
    "postman": "https://check-wireguard.graphai.eu/api/queries?download=postman",
    "openapi": "https://check-wireguard.graphai.eu/api/queries?download=openapi"
  },
  "queries": [
    { "name": "by_country", "href": "/api/queries?name=by_country" }
  ]
}

GET /api/queries?name=by_country&days=30
GET /api/queries/by_isp?days=7
GET /api/queries?name=scans_recent&limit=50
GET /api/queries?name=wireguard_share
GET /api/queries?name=rtt_percentiles
GET /api/queries?name=geo_points
GET /api/queries?name=hourly_volume
GET /api/queries?name=by_protocol
GET /api/queries?name=by_kind
GET /api/queries?name=quality_mix
GET /api/queries?name=dns_filter
GET /api/queries?name=logs_recent
GET /api/queries?name=logs_by_app
GET /api/queries?name=logs_errors

POST /api/logs
Content-Type: application/json

{ "app": "jobs.graphai.eu", "logs": [{ "level": "error", "message": "…", "href": "https://jobs.graphai.eu/" }] }
Try by_country Try wireguard_share Try scans_recent