# Carconomics Observed rental-market reports for Chicago (ORD) and Los Angeles (LAX). Website: https://carconomics.com Report overview: https://carconomics.com/reports API reference: https://carconomics.com/openapi.json Stripe profile: https://stripe.com/@carconomics Support: support@carconomics.com ## Offers - 1 market report: USD 39, plus applicable tax. - 3 market reports: USD 79, plus applicable tax. - 5 market reports: USD 109, plus applicable tax. One-time purchases, no subscription. Each credit builds one report for one supported group and market. Credits and saved reports last 400 days after payment. The first report is delivered with payment; remaining credits may be used later. Reports describe observed prices and listing changes, not bookings, occupancy, host earnings or guaranteed rental availability. A report has at most 8 cohorts and 14 forward dates. It retains the observation timestamp and labels. ## 1. Prepare Link before starting the quote clock No Carconomics account or merchant browser checkout is required. Payments use Stripe MPP card Shared Payment Tokens, not the stripe pay business transfer. Use Node.js 22 or newer and the official CLI: https://github.com/stripe/link-cli. An existing authorized Link session can be reused. Initial Link sign-in and any spending approval happen through Link's verification URL/app, not a Carconomics checkout form. Complete setup before creating a short-lived quote. The examples below use a POSIX shell. Keep command tracing off (do not use set -x). Install the official CLI if needed, then use its trusted script path: ```sh npm install --global @stripe/link-cli export CC_LINK_CLI="$(npm root -g)/@stripe/link-cli/dist/cli.js" ``` If the buyer or calling workflow supplies LINK_AUTH_FILE, keep that same absolute path exported for authentication, status polling, payment and every continuation, including a resumed shell. Reuse it across merchants; do not copy the credentials, switch to another file or create a Carconomics-specific wallet. When LINK_AUTH_FILE is unset, leave it unset and let Link CLI use its platform default storage. Do not add an overriding --auth flag. All child processes below inherit this environment. Validate a supplied path without opening it, then check the existing session: ```sh node --input-type=module <<'NODE' import { isAbsolute } from 'node:path'; if (process.env.LINK_AUTH_FILE !== undefined && !isAbsolute(process.env.LINK_AUTH_FILE)) { throw new Error('Keep the caller-supplied LINK_AUTH_FILE exported as an absolute path.'); } NODE node "$CC_LINK_CLI" auth status --format json --filter-output authenticated ``` If the final value is true, reuse the session and skip login. The status command reports the selected local session; it is not purchase approval or proof that a later provider request will succeed. LINK_ACCESS_TOKEN, when supplied by the caller, takes precedence over file-based status; do not silently replace it. Only when authentication is needed and no login is already pending, start it once. An interval of zero returns the verification URL immediately instead of buffering it while waiting. The output filter exposes only the URL and phrase: ```sh node "$CC_LINK_CLI" auth login --interval 0 --format json --filter-output verification_url,phrase ``` Present verification_url and phrase to the buyer. If login was already started in another workflow, use its existing verification prompt and the same auth file. Poll that session, without running login again: ```sh node "$CC_LINK_CLI" auth status --interval 2 --timeout 300 --max-attempts 150 --format json --filter-output authenticated ``` Polling stops on authentication, the attempt cap or timeout. With --format json the results arrive as a buffered array; continue only if the final value is true. A false result or command exit alone is not success. Polling can finish an already-approved pending login and save it to the same file. Never print raw authentication output: successful results can include an access-token prefix. Keep these filters and do not use --verbose. Buyer login and approval of each purchase are separate. Never choose a card or approve spending without the buyer's authority. Do not add --test to a production purchase. ## 2. Discover and preview for free ```sh curl --fail-with-body https://carconomics.com/api/agents/offers curl --fail-with-body 'https://carconomics.com/api/reports/preview?group=tesla&market=lax' ``` Read payment.status first. If unavailable, stop. The catalog lists preview URLs; choose one returning status=ok and review its observations/coverage. Thin, stale or paused markets cannot be purchased. Luxury is currently paused. A successful catalog response alone does not establish report availability. ## 3. Save one private purchase identity and the exact quote request Choose a new private directory for this intended purchase, outside source control and shared folders, and set CC_REPORT_ORDER_DIR to its absolute path. Use umask 077 and directory permissions 0700. Keep this path for retries; do not run this setup again for an existing purchase. Losing the saved key prevents automatic recovery. The key is sent only in X-Carconomics-Order-Key, never in a URL. Create selection.json in that directory with these JSON fields: pack: 1, 3 or 5 group: "tesla" market: "ord" or "lax" billingAddress: {country, postalCode, state?, city?, line1?, line2?} termsAccepted: true Use the buyer's actual billing location, including the state where applicable; never substitute the report's market. Review https://carconomics.com/terms and obtain the buyer's agreement before setting termsAccepted. Keep this file private. Run once to persist a UUID, 32 random bytes encoded as lowercase hex, and the exact request body. Existing purchase.json is deliberately not replaced: ```sh node --input-type=module <<'NODE' import { readFileSync, writeFileSync, chmodSync } from 'node:fs'; import { randomUUID, randomBytes } from 'node:crypto'; import { join, isAbsolute } from 'node:path'; const dir = process.env.CC_REPORT_ORDER_DIR; if (!dir || !isAbsolute(dir)) throw new Error('Set an absolute private CC_REPORT_ORDER_DIR.'); chmodSync(dir, 0o700); const selection = JSON.parse(readFileSync(join(dir, 'selection.json'), 'utf8')); if (selection.termsAccepted !== true) throw new Error('Buyer terms agreement is required.'); const orderId = randomUUID(); const purchase = { orderId, key: randomBytes(32).toString('hex'), requestBody: JSON.stringify({ ...selection, orderId }) }; writeFileSync(join(dir, 'purchase.json'), JSON.stringify(purchase), { flag: 'wx', mode: 0o600 }); console.log('Purchase identity saved privately.'); NODE ``` Create the tax quote with that saved body. A retry uses the same file unchanged: ```sh node --input-type=module <<'NODE' import { readFileSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; const dir = process.env.CC_REPORT_ORDER_DIR; const purchase = JSON.parse(readFileSync(join(dir, 'purchase.json'), 'utf8')); const response = await fetch('https://carconomics.com/api/agents/orders', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Carconomics-Order-Key': purchase.key }, body: purchase.requestBody, }); const quote = await response.json(); writeFileSync(join(dir, 'quote.json'), JSON.stringify(quote), { mode: 0o600 }); console.log(JSON.stringify({ httpStatus: response.status, status: quote.status, price: quote.price, expiresAt: quote.expiresAt, preview: quote.preview })); NODE ``` Continue only on HTTP 200 with status=quoted. Review the returned preview and subtotal/tax/total in USD cents, and obtain approval for that exact total. Quotes last at most 15 minutes; use expiresAt. A processing/paid replay belongs to the existing order: use recovery instead of starting another spend request. ## 4. Start Link payment and keep raw CLI output private The command is mpp pay with --method POST, --data '{}', the private header, --amount equal to the quoted total, a meaningful --context of at least 100 characters, and --format json. Capture stdout/stderr privately: Link's returned continuation repeats the private order header. Never paste raw CLI output, continuation arguments or child-process error objects into a transcript. This invocation uses arguments directly, with no shell interpolation: Keep the original LINK_AUTH_FILE environment unchanged; the invocation and its approval/payment continuations inherit it automatically. ```sh node --input-type=module <<'NODE' import { readFileSync, writeFileSync } from 'node:fs'; import { execFileSync } from 'node:child_process'; import { join, isAbsolute } from 'node:path'; const dir = process.env.CC_REPORT_ORDER_DIR, cli = process.env.CC_LINK_CLI; if (!cli || !isAbsolute(cli)) throw new Error('Set the trusted absolute CC_LINK_CLI script path.'); const purchase = JSON.parse(readFileSync(join(dir, 'purchase.json'), 'utf8')); const quote = JSON.parse(readFileSync(join(dir, 'quote.json'), 'utf8')); const payUrl = new URL(quote.payUrl); if (quote.status !== 'quoted' || Date.parse(quote.expiresAt) <= Date.now() || payUrl.origin !== 'https://carconomics.com' || payUrl.pathname !== '/api/agents/pay' || payUrl.searchParams.get('orderId') !== purchase.orderId) throw new Error('Recover this order before replacing an expired unpaid quote.'); try { const raw = execFileSync(process.execPath, [cli, 'mpp', 'pay', payUrl.href, '--method', 'POST', '--data', '{}', '--header', 'X-Carconomics-Order-Key: ' + purchase.key, '--amount', String(quote.price.total), '--context', 'Purchase the approved Carconomics market report pack at the exact quoted USD total including tax; return the report and keep the recovery details private.', '--format', 'json'], { shell: false, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }); writeFileSync(join(dir, 'link-start.json'), raw, { mode: 0o600 }); const results = JSON.parse(raw); if (!Array.isArray(results) || results.length !== 1) throw new Error('Unexpected Link output; recover the saved order.'); const result = results[0]; console.log(JSON.stringify({ status: result.status, approvalUrl: result.approval_url, needsContinuation: Boolean(result._next) })); } catch { console.error('Link command did not confirm delivery. Keep this order and use recovery; do not print the raw error.'); process.exitCode = 1; } NODE ``` If approval is requested, present only approval_url to the buyer. Keep link-start.json private. In agent mode the first call can return a spend request, not a paid report. Follow _next.poll_argv until the spend request is approved, then _next.pay_argv. Preserve every returned argument, including the approved challenge and private header. Each value is {command, args}; command is a Link subcommand, not an executable. Do not rerun the initial mpp pay command to poll. The installed CLI's --format json output is an array of yielded results; the initial payment call has one result. Polling can yield several. The example below polls the saved request, then pays only after the same request is approved. Run it after presenting the approval URL; it waits for Link's status change. The saved initial result retains the spend-request ID and complete continuation for later recovery. Never join/re-split arguments, execute _next.pay_command with a shell, or log the arrays. Denied, expired, requires_action and error results do not authorize a payment. Complete any further Link verification before resuming; if the quote expires, recover the order before replacing it. ```sh node --input-type=module <<'NODE' import { readFileSync, writeFileSync } from 'node:fs'; import { execFileSync } from 'node:child_process'; import { join, isAbsolute } from 'node:path'; const dir = process.env.CC_REPORT_ORDER_DIR, cli = process.env.CC_LINK_CLI; if (!cli || !isAbsolute(cli)) throw new Error('Set the trusted absolute CC_LINK_CLI script path.'); const options = { shell: false, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 8 * 1024 * 1024 }; function lastResult(raw) { const results = JSON.parse(raw); if (!Array.isArray(results) || !results.length) throw new Error('Unexpected Link output.'); return results.at(-1); } function continueLink(next, file) { if (!next || !Array.isArray(next.args) || !next.args.every(arg => typeof arg === 'string')) throw new Error('Invalid continuation.'); const raw = execFileSync(process.execPath, [cli, next.command, ...next.args, '--format', 'json'], options); writeFileSync(join(dir, file), raw, { mode: 0o600 }); return lastResult(raw); } try { let result = lastResult(readFileSync(join(dir, 'link-start.json'), 'utf8')); if (result._next) { const { poll_argv: poll, pay_argv: pay } = result._next; if (poll?.command !== 'spend-request' || poll.args?.[0] !== 'retrieve' || pay?.command !== 'mpp' || pay.args?.[0] !== 'pay') throw new Error('Unexpected continuation.'); const approval = continueLink(poll, 'link-poll.json'); if (approval.id !== result.id || approval.status !== 'approved') { console.log(JSON.stringify({ approvalStatus: approval.status, approvalUrl: approval.approval_url })); throw new Error('This request is not approved.'); } const quote = JSON.parse(readFileSync(join(dir, 'quote.json'), 'utf8')); if (!Number.isFinite(Date.parse(quote.expiresAt)) || Date.parse(quote.expiresAt) <= Date.now()) throw new Error('Recover the expired quote before another attempt.'); result = continueLink(pay, 'link-paid.json'); } const body = JSON.parse(result.body); writeFileSync(join(dir, 'payment-result.json'), JSON.stringify({ body, receipt: result.headers?.['payment-receipt'] }), { mode: 0o600 }); console.log(JSON.stringify({ httpStatus: result.status, status: body.status, orderId: body.orderId, requestId: body.requestId, deliveredAt: body.deliveredAt, report: body.report, creditsRemaining: body.creditsRemaining })); if (result.status !== 200 || body.status !== 'paid' || !body.report) process.exitCode = 1; } catch { console.error('Delivery is not confirmed. Keep all saved files and recover this order before any replacement payment.'); process.exitCode = 1; } NODE ``` Link's completed HTTP result contains status, headers and a JSON-string body. Only HTTP status=200 plus parsed body.status='paid' and body.report establish first-report delivery. A CLI exit code or approved spend request alone is not payment/delivery proof. The example also handles an initial HTTP result without _next. If using --format jsonl instead, data is in events with type='chunk'; type='done' is command completion, and type='error' must stop the flow. ## 5. Recover after interruption before attempting another payment Recover using the original purchase.json; this makes no new charge: ```sh node --input-type=module <<'NODE' import { readFileSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; const dir = process.env.CC_REPORT_ORDER_DIR; const purchase = JSON.parse(readFileSync(join(dir, 'purchase.json'), 'utf8')); const url = new URL('https://carconomics.com/api/agents/orders'); url.searchParams.set('orderId', purchase.orderId); const response = await fetch(url, { headers: { 'X-Carconomics-Order-Key': purchase.key } }); const result = await response.json(); writeFileSync(join(dir, 'recovered-order.json'), JSON.stringify(result), { mode: 0o600 }); console.log(JSON.stringify({ httpStatus: response.status, status: result.status, orderId: result.orderId, requestId: result.requestId, report: result.report, deliveredAt: result.deliveredAt, creditsRemaining: result.creditsRemaining })); NODE ``` - paid: the exact first report is restored without another credit spend. - quoted: payment is not confirmed; an existing Link approval may still need its saved pay continuation. Do not create a duplicate spend request. - processing, needs-review, busy, transport failure or 503: outcome is unresolved. Retain the original order/key and recover again or contact support. No new charge. - payment-failed: the provider proved terminal cancellation without received funds. A separately approved replacement can use a new UUID and private key. - payment-rejected: the attempted provider request was rejected. Recover the same order before deciding whether to retry; this is not delivery confirmation. - expired: an unpaid quote cannot start payment. First recover any outstanding attempt; only a confirmed unpaid/expired purchase can be replaced. Paid-order access also expires after 400 days. - refunded: access is revoked. 404 means unknown ID or incorrect/missing key; it does not prove an earlier payment failed. Fix recovery credentials first. ## 6. Use remaining credits or recover a specific report Preview the desired current market again before spending a remaining credit. Save report-request.json privately with {orderId, requestId, group, market}: orderId is the existing purchase; generate one new UUID for requestId only when requesting a new report. Use group="tesla" and a supported market. Keep the same request file unchanged after a timeout; a new UUID would spend another credit. ```sh node --input-type=module <<'NODE' import { readFileSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; const dir = process.env.CC_REPORT_ORDER_DIR; const purchase = JSON.parse(readFileSync(join(dir, 'purchase.json'), 'utf8')); const body = readFileSync(join(dir, 'report-request.json'), 'utf8'); if (JSON.parse(body).orderId !== purchase.orderId) throw new Error('Report request must use the saved order.'); const response = await fetch('https://carconomics.com/api/agents/reports', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Carconomics-Order-Key': purchase.key }, body, }); const result = await response.json(); writeFileSync(join(dir, 'report-response.json'), JSON.stringify(result), { mode: 0o600 }); console.log(JSON.stringify({ httpStatus: response.status, status: result.status, requestId: result.requestId, report: result.report, creditsRemaining: result.creditsRemaining, replayed: result.replayed })); NODE ``` HTTP 200 with status=ok and report confirms delivery. For a read-only saved report lookup use GET https://carconomics.com/api/agents/reports?orderId=ORDER_UUID&requestId=REPORT_UUID with the same private key header. A same-UUID/same-scope replay spends nothing; request-mismatch or credit-required must not be retried with invented UUIDs. Saved reports keep their original observations. Refunds/disputes disable access. Contact support with the order UUID, never the private key. ## Subscription plans (discoverable; embedded signup) Renter Pro: USD 10 per month, plus applicable tax. Host Pro: USD 29 per month, or USD 228 billed each year, plus applicable tax. These subscriptions renew automatically and require a verified Carconomics account. Compare and subscribe at https://carconomics.com/plans using embedded Stripe checkout. Recurring signup is not enabled through this MPP report API. Do not treat a one-time report payment as subscription consent or account access. Fleet and beta-only prices are not public agent offers. ## Human checkout People can use the embedded Stripe checkout at https://carconomics.com/plans. Human account report credits and agent order credits are separate.