← Subpay

Document Reading API

Send a construction pay application — an AIA G702 Application and Certificate for Payment with its G703 Continuation Sheet — or a change order PDF. Get back the figures as JSON, with arithmetic checks. $0.04 per page (up to 25 pages) in USDC per successful read over x402, on Base or Solana. No account, no API key, no subscription. Failed reads are never charged.

What it reads

  • Pay applications: the AIA G702 Application and Certificate for Payment with its G703 Continuation Sheet, and the Procore and Textura versions of them. Returns application number, period, gross this period, retainage held and released, retainage rate, completed to date, contract sum to date, balance to finish, current payment due, and the change-order lines the GC lists.
  • Change orders: tells a subcontractor's request for change order apart from the general contractor's executed change order, and returns the issuer, number, description, amount, and date.

There is no reviewer on a paid read, so every response carries the checks a reviewer would make — the G702's own arithmetic, the first-application gross rule, and the retainage-billing rule — plus plain-language warnings when one fails. Verify figures against the document before relying on them.

Quick start

1. Describe the service (free):

curl https://subpay.ai/api/v1/extract

2. Call it with any x402 client. The first request gets 402 Payment Required naming the price and where to pay; the client pays and retries automatically. In TypeScript, with @x402/fetch:

import { readFileSync } from "node:fs";
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";

// A wallet holding USDC on Base. It needs no ETH: the facilitator pays gas.
const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as `0x${string}`);

const pay = wrapFetchWithPaymentFromConfig(fetch, {
  schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }],
});

const res = await pay("https://subpay.ai/api/v1/extract", {
  method: "POST",
  headers: { "Content-Type": "application/pdf" },
  body: readFileSync("pay-app.pdf"),
});
console.log(res.status, await res.json());

Paying on Solana instead — register the Solana scheme:

import { ExactSvmScheme } from "@x402/svm/exact/client";
import { toClientSvmSigner } from "@x402/svm";

const pay = wrapFetchWithPaymentFromConfig(fetch, {
  schemes: [{
    network: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
    client: new ExactSvmScheme(toClientSvmSigner(yourSolanaKeypairSigner)),
  }],
});

MCP server

The same service is an MCP server at https://subpay.ai/mcp (Streamable HTTP), for agents that use tools rather than call web APIs. Tools: extract_document (paid, $0.04 per page (up to 25 pages) per successful call, quoted before payment; the PDF base64-encoded in document, optional fileName, subcontractor, gc) and about (free). Payment is per tool call over x402; a call that fails is not charged.

import { createx402MCPClient } from "@x402/mcp";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const client = createx402MCPClient({
  name: "my-agent",
  version: "1.0.0",
  schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }],
  autoPayment: true,
});
await client.connect(new StreamableHTTPClientTransport(new URL("https://subpay.ai/mcp")));

const result = await client.callTool("extract_document", {
  document: readFileSync("pay-app.pdf").toString("base64"),
});

Request

POST https://subpay.ai/api/v1/extract with the PDF in any of three forms:

  • the raw PDF, Content-Type: application/pdf
  • multipart form data with the PDF in a field named file
  • JSON with the PDF base64-encoded in document:
POST https://subpay.ai/api/v1/extract
Content-Type: application/json

{
  "document": "<the PDF, base64-encoded>",
  "fileName": "pay-app-27.pdf",
  "subcontractor": "Acme Signs LLC",
  "gc": "Skanska"
}

subcontractor and gc are optional (JSON fields, or query parameters with the other two forms). Naming the parties sharpens telling a subcontractor's request from the GC's change order.

Response

A pay application:

{
  "document": {
    "documentType": "PAY_APPLICATION",
    "applicationNumber": "27",
    "periodTo": "2026-06-30",
    "grossThisPeriod": 41819.65,
    "retainageHeldToDate": 4181.96,
    "retainageReleasedThisPeriod": null,
    "retainageRatePercent": 10,
    "totalCompletedAndStoredToDate": 41819.65,
    "contractSumToDate": 63388,
    "balanceToFinishIncludingRetainage": 25750.31,
    "currentPaymentDue": 37637.69,
    "changeOrders": []
  },
  "checks": [
    { "name": "g702_math", "passed": true,
      "detail": "contract sum to date − completed to date (+ retainage held) = balance to finish" },
    { "name": "first_application_gross", "passed": true,
      "detail": "first application: gross this period should equal total completed & stored to date" }
  ],
  "warnings": [],
  "confidence": 0.95
}

A change order:

{
  "document": {
    "documentType": "CHANGE_ORDER",
    "kind": "GC_CHANGE_ORDER",
    "issuer": "Balfour Beatty",
    "number": "004",
    "description": "CE #705 - WTC Garage Scope Gaps - Clearance Bar at 10th Floor Parking Garage",
    "amount": 2405.69,
    "date": "2025-09-14"
  },
  "checks": [],
  "warnings": [],
  "confidence": 0.95
}

kind is SUBCONTRACTOR_REQUEST for a request for change order (not yet part of the contract) or GC_CHANGE_ORDER for the general contractor's executed change order. On a retainage billing — no new work, held retainage being paid out — grossThisPeriod is 0 and the payout is in retainageReleasedThisPeriod. Figures not printed on the document are null.

Payment

NetworkCAIP-2 idAsset
Baseeip155:8453USDC 0x8335…2913
Solanasolana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpUSDC EPjF…Dt1v
  • Price: $0.04 per page (up to 25 pages), charged per successful read (HTTP status below 400). Subpay counts the PDF's pages first, and the 402 response quotes that document's exact price and the receiving address — a 7-page pay app is $0.28. That response governs.
  • PDFs over 25 pages are refused with 413 and not charged; split them and send the pages you need. 25 pages tops out at $1.00, the default per-payment limit in standard x402 clients.
  • Failed reads are not charged: the payment is verified before the read and settled only after a successful response.
  • Your wallet needs only USDC. Network fees are paid by the facilitator.

Errors

None of these are charged.

400No PDF in the request, or the wrong field name
402Payment required — your x402 client handles this
413PDF larger than 4 MB, or more than 25 pages
415The body isn't a PDF
422Not a pay application or change order (e.g. a lien waiver) — see below
502The document couldn't be read
503Reading temporarily unavailable

Need a different document?

Documents other than pay applications and change orders — lien waivers, invoices, schedules of values — are refused with 422 and not charged, rather than read wrongly. Tell us what you need and what you'd pay per read, and it shapes what we build next:

POST https://subpay.ai/api/v1/requests
Content-Type: application/json

{
  "documentType": "conditional lien waiver",
  "fieldsNeeded": "claimant, through date, amount",
  "monthlyVolume": 200,
  "maxPricePerReadUsd": 0.25
}

Free. Over MCP, call request_capability. Please leave out personal information.

Limits and data

  • PDF only, up to 4 MB (about 3 MB if sent base64 in JSON). Scanned pages are supported.
  • A read typically takes 5–30 seconds.
  • Documents are read and discarded — never stored, never used for training. No Subpay account data is reachable through the API.

Machine-readable: OpenAPI spec and llms.txt.

Use of the API is governed by the API Terms of Use and the Privacy Policy. Questions: support@subpay.ai.