← Subpay

Document Reading API

Send a construction pay application or change order PDF. Get back the figures as JSON, with arithmetic checks. $0.20 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: AIA G702/G703 and platform pay apps (Procore, Textura and similar). 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.20 per successful call; 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.20 per successful read (HTTP status below 400). The 402 response states the current price and receiving address; that response governs.
  • 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.
  • Standard x402 clients cap a single payment at $1 by default, so no change is needed to pay this API.

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
415The body isn't a PDF
502The document couldn't be read
503Reading temporarily unavailable

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.