# GroundSignal buyer CLI guide Read and run each shell block separately in the same shell. The live catalog supplies available offers, prices, covered towns, sources, dates, limits and delivery details. 1. Inspect the catalog and coverage stripe directory search "GroundSignal" --format json curl --fail --silent --show-error \ https://cookcountyrecords.com/api/agent/offers | jq . 2. Request a recoverable quote Use the buyer's US billing address for tax. Choose appeal_brief for one eligible PIN or trade_list or season_pass for one covered town. The quote checks the available records, returns the deliverable and full total in cents, and sets an expiry. The Link limit is $500 including tax. # Run these commands in one shell. Requires curl, jq, OpenSSL and uuidgen. umask 077 ORIGIN=https://cookcountyrecords.com PURCHASE_ROOT="$HOME/.local/share/groundsignal/purchases" mkdir -p "$PURCHASE_ROOT" PURCHASE_DIR=$(mktemp -d "$PURCHASE_ROOT/purchase.XXXXXX") openssl rand -hex 32 > "$PURCHASE_DIR/access-token" uuidgen > "$PURCHASE_DIR/request-key" printf 'SKU (appeal_brief, trade_list, season_pass): ' read -r SKU printf 'Town slug from the available catalog: ' read -r CITY PIN= if [ "$SKU" = appeal_brief ]; then printf 'Property PIN (14 digits, no separators): ' read -r PIN fi printf 'US billing street address: ' read -r BILLING_LINE1 printf 'Billing city: ' read -r BILLING_CITY printf 'Billing state (two uppercase letters): ' read -r BILLING_STATE printf 'Billing ZIP code: ' read -r BILLING_POSTAL jq -n --arg sku "$SKU" --arg city "$CITY" --arg pin "$PIN" \ --arg line1 "$BILLING_LINE1" --arg billingCity "$BILLING_CITY" \ --arg state "$BILLING_STATE" --arg postal "$BILLING_POSTAL" \ '{sku:$sku, city:$city, billing_address:{country:"US", line1:$line1, city:$billingCity, state:$state, postal_code:$postal}} + (if $sku == "appeal_brief" then {pin:$pin} else {} end)' \ > "$PURCHASE_DIR/request.json" curl --fail-with-body --silent --show-error \ "$ORIGIN/api/agent/quotes" \ --header "Authorization: Bearer $(cat "$PURCHASE_DIR/access-token")" \ --header "Idempotency-Key: $(cat "$PURCHASE_DIR/request-key")" \ --header 'Content-Type: application/json' \ --data-binary @"$PURCHASE_DIR/request.json" \ --output "$PURCHASE_DIR/quote.json" # Review all amounts in cents, coverage, record dates, delivery and expiry. # Keep the access token and secure status link out of shared logs. jq 'del(.secure_status_link)' "$PURCHASE_DIR/quote.json" printf 'Recovery files saved in %s\n' "$PURCHASE_DIR" 3. Connect the buyer's wallet and authorize the reviewed total Review the town or PIN, sources, dates, scope, license, price, tax and final total with the buyer. Complete wallet login and approve the displayed phrase in Link. Use the same LINK_AUTH_FILE for every Link command if one was set. # Install Link CLI first if needed: npm install --global @stripe/link-cli # Use a current Node.js LTS such as 22.22 or 24 with the npm package. # If your wallet login uses LINK_AUTH_FILE, export that same path here. # Keep it set for login, status, payment, polling and resume commands. link-cli auth login # Open Link's login URL and approve its displayed phrase when prompted. # Continue only after authenticated is true; this does not approve a purchase. link-cli auth status --format json PAYMENT_URL=$(jq -er '.payment_url' "$PURCHASE_DIR/quote.json") QUOTE_ID=$(jq -er '.id' "$PURCHASE_DIR/quote.json") TOTAL_CENTS=$(jq -er '.totals.total' "$PURCHASE_DIR/quote.json") # An agent must ask its buyer to approve the complete quoted total first. printf 'After reviewing the complete total and scope, type PAY to authorize: ' read -r APPROVAL if [ "$APPROVAL" = PAY ]; then link-cli mpp pay "$PAYMENT_URL" \ --method POST --data '{}' --amount "$TOTAL_CENTS" \ --header "Authorization: Bearer $(cat "$PURCHASE_DIR/access-token")" \ --context "Purchase reviewed property-research quote $QUOTE_ID for a complete total of $TOTAL_CENTS USD cents including applicable tax. The buyer approved the town coverage, source dates, deliverable, license, and limitations." \ --format json > "$PURCHASE_DIR/payment.json" # Show approval details privately to the buyer; never print continuation args. jq '(if type == "object" and has("data") then .data else . end) | (if type == "array" then .[-1] else . end) | {spend_request_id:.id, status, approval_url, action:(.status_details.requires_action.next_action | if . == null then null else {type,resolution,display_message,action_url} end)}' \ "$PURCHASE_DIR/payment.json" fi 4. If Link requests approval, resume the same purchase Keep the response and continuation arguments private. If the first payment already completed, go directly to order recovery. After a lost response, check the saved status URL before resuming. This example requires Python 3. # Run only when Link returns an approval URL. Keep LINK_AUTH_FILE unchanged. # The buyer opens that URL and approves the displayed purchase in Link. python3 - "$PURCHASE_DIR" <<'PY' import json, subprocess, sys from datetime import datetime, timezone from pathlib import Path directory = Path(sys.argv[1]) def read_result(filename): value = json.loads((directory / filename).read_text()) if isinstance(value, dict) and "data" in value: value = value["data"] return value[-1] if isinstance(value, list) else value def run_link(command, args, filename): with (directory / filename).open("w") as output, (directory / "link-errors.log").open("a") as errors: code = subprocess.run(["link-cli", command, *args, "--format", "json"], stdout=output, stderr=errors).returncode if code: sys.exit("Link did not complete. Recover this order and inspect its private response.") return read_result(filename) pending = read_result("payment.json") quote = read_result("quote.json") steps = pending.get("_next", {}) poll = steps.get("poll_argv", {}) pay = steps.get("pay_argv", {}) spend_id = pending.get("id") if not spend_id or poll.get("command") != "spend-request" or poll.get("args", [])[:2] != ["retrieve", spend_id]: sys.exit("No matching approval request. Check the existing order status.") args = pay.get("args", []) if (pay.get("command") != "mpp" or args[:2] != ["pay", quote["payment_url"]] or "--spend-request-id" not in args or "--approved-challenge" not in args or args[args.index("--spend-request-id") + 1 :][:1] != [spend_id]): sys.exit("The continuation does not match this quote and approval request.") approved = run_link("spend-request", poll["args"], "approval.json") if approved.get("id") != spend_id or approved.get("status") != "approved": sys.exit("Link has not confirmed approval. Keep the quote and check its status.") if datetime.fromisoformat(quote["expires_at"].replace("Z", "+00:00")) <= datetime.now(timezone.utc): sys.exit("The quote expired. Recover the existing order before requesting a new quote.") run_link("mpp", args, "payment-resumed.json") print("Link returned. Recover the same order below to verify payment and delivery.") PY 5. Recover the existing order and download the result Use the saved order number, paid total, status and secure status link. Processing means preparation is still underway. Download only after the merchant returns state=ready, paid=true and completed=true. For an appeal brief, structured_download_url also provides JSON through the same private key. # In a new shell, set PURCHASE_DIR to the directory saved above. STATUS_URL=$(jq -er '.status_url' "$PURCHASE_DIR/quote.json") curl --fail-with-body --silent --show-error "$STATUS_URL" \ --header "Authorization: Bearer $(cat "$PURCHASE_DIR/access-token")" \ --output "$PURCHASE_DIR/status.json" jq 'del(.secure_status_link)' "$PURCHASE_DIR/status.json" # A quote's download_url is null until the order is ready. if jq -e '.state == "ready" and .paid == true and .completed == true' \ "$PURCHASE_DIR/status.json" > /dev/null; then DOWNLOAD_URL=$(jq -er '.download_url' "$PURCHASE_DIR/status.json") if [ "$(jq -r '.sku' "$PURCHASE_DIR/request.json")" = appeal_brief ]; then RESULT_FILE=report.html else RESULT_FILE=list.csv fi curl --fail --silent --show-error "$DOWNLOAD_URL" \ --header "Authorization: Bearer $(cat "$PURCHASE_DIR/access-token")" \ --output "$PURCHASE_DIR/$RESULT_FILE" else printf 'Order is not ready. Keep its recovery files and check this order again.\n' fi 6. Retrieve the included rebuild for the same PIN or town # Included with the same paid order; no payment command is needed. # Check status as above, then use its refresh_url. REFRESH_URL=$(jq -er '.refresh_url' "$PURCHASE_DIR/status.json") curl --fail-with-body --silent --show-error "$REFRESH_URL" \ --header "Authorization: Bearer $(cat "$PURCHASE_DIR/access-token")" \ --dump-header "$PURCHASE_DIR/rebuild-headers.txt" \ --output "$PURCHASE_DIR/current-$RESULT_FILE" A lost quote response can be recovered by repeating the identical request with the original access token and Idempotency-Key. A full refund revokes downloads. Keep original sources, dates, assessment calculations, privacy restrictions and uncertainty labels with the result. Human checkout and account entitlements are available at https://cookcountyrecords.com/pricing Read the complete buyer guide at https://cookcountyrecords.com/agents Check coverage and source dates at https://cookcountyrecords.com/coverage For order support, send the order number to support@cookcountyrecords.com.