DeedPro API

Submit the facts of a California deed from your platform. The API returns a draft — an id, a status, and a confirmation URL — not a stored PDF. A person you name opens that URL, sees the deed as it will print, and approves or sends it back. DeedPro does not confirm facts and never will. This is the mechanism by which your human does.

Base URL https://deedpro-main-api.onrender.com/api/v1

Quickstart

Four steps from a key to a stored PDF. The third step is a human.

  1. 1. Get a test key. Request access — three fields, no account needed. We issue keys after a short conversation about what you’re building. Test keys start with dp_test_.
  2. 2. POST the transaction. One request carries the property, the parties, the transfer tax declaration, the recording block, and the named approver. Incomplete facts fail here.
  3. 3. Deliver the confirmation URL. You give the link to the person you named. They see the rendered deed — not a summary — and approve or reject it with a reason.
  4. 4. Download the PDF after approval. Until then, GET /deeds/{id}/pdf returns 409.
Create a draft
curl -X POST https://deedpro-main-api.onrender.com/api/v1/deeds \
  -H "Authorization: Bearer dp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-48219" \
  -d '{
    "deed_type": "grant_deed",
    "property": {
      "address": "1234 Sycamore Lane",
      "city": "Los Angeles",
      "state": "CA",
      "zip": "90001",
      "county": "Los Angeles",
      "apn": "5432-001-012",
      "legal_description": "LOT 15, BLOCK 3, TRACT 12345, IN THE CITY OF LOS ANGELES..."
    },
    "grantor": { "name": "JOHN A. DOE AND JANE B. DOE, HUSBAND AND WIFE" },
    "grantee": { "name": "ROBERT C. ROE", "vesting": "a single man" },
    "transfer_tax": {
      "exempt": false,
      "value": 750000,
      "computed_amount": "825.00",
      "basis": "full_value",
      "city_tax": true,
      "city_name": "Los Angeles"
    },
    "recording": {
      "requested_by": "Pacific Coast Escrow",
      "title_order_no": "TO-88231",
      "escrow_no": "ESC-44120",
      "return_to": {
        "name": "ROBERT C. ROE",
        "address": "1234 Sycamore Lane",
        "city": "Los Angeles",
        "state": "CA",
        "zip": "90001"
      }
    },
    "approver": {
      "name": "Jane Roe",
      "role": "escrow officer",
      "email": "jane@escrow.example"
    }
  }'
Response
{
  "success": true,
  "data": {
    "deed_id": "deed_8Kd2mQxR4vLp",
    "document_id": "DOC-2026-H7K3M",
    "deed_type": "grant_deed",
    "status": "pending_confirmation",
    "expires_at": "2026-09-03T18:22:41Z",
    "urls": {
      "confirmation": "https://deedpro.io/confirm/…",
      "pdf": null,
      "verification": null
    },
    "approver": { "name": "Jane Roe", "role": "escrow officer" },
    "property": { "address": "1234 Sycamore Lane, Los Angeles, CA 90001", ... },
    "parties": { "grantor": "JOHN A. DOE AND JANE B. DOE", "grantee": "ROBERT C. ROE" }
  }
}

Python

import requests

resp = requests.post(
    "https://deedpro-main-api.onrender.com/api/v1/deeds",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Idempotency-Key": order_id,   # safe to retry
    },
    json=deed_payload,
    timeout=60,                        # PDFs render server-side
)
resp.raise_for_status()
deed = resp.json()["data"]
# Deliver deed["urls"]["confirmation"] to the named approver.
# A stored PDF exists only after they approve.

Node

const res = await fetch("https://deedpro-main-api.onrender.com/api/v1/deeds", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
    "Idempotency-Key": orderId,   // safe to retry
  },
  body: JSON.stringify(deedPayload),
});

if (!res.ok) {
  const { detail } = await res.json();
  throw new Error(`${detail.code}: ${detail.message}`);
}

const { data } = await res.json();

Confirmation

This is why the API exists in this shape. DeedPro formats and records. It does not confirm facts. The old one-call generate — POST, get a stored PDF, done — is dead. That path let a machine produce a recordable instrument with no human in the loop, which voided the product’s central promise.

What this preserves: nothing reaches a stored instrument unseen. What it costs: you cannot treat generate as a synchronous one-call download. You hold the source data. If the human says the deed is wrong, you correct it in your system and POST again with a new Idempotency-Key. Replaying a rejected key returns the rejected draft. It does not mint a new one.

  • Named for the record, token for auth. Send approver.name and approver.role. Optional email is stored, never mailed in v1, and purged after the same contact-retention window as signer contact. You deliver the link.
  • Seven days. An unapproved draft expires. Preview bytes are dropped. The token then shows expired.
  • The bytes they approved are the bytes stored. The deed is rendered once at create. Approval promotes those preview bytes and stamps the hash. Re-rendering would store a different document than the one they saw.
  • Reject, do not edit. The confirmation page is not a second builder. The human sends a reason. You change the facts where they live.

Authentication

Every request carries your key as a bearer token:

Authorization: Bearer dp_live_...
  • Test and live keys. dp_test_ and dp_live_. Both generate real PDFs on the same templates; test keys exist so you can build and demo without your traffic mixing into live records.
  • Shown once. Keys are stored hashed. The full value appears exactly once, when it is created — if it is lost, we issue a new one rather than recovering the old.
  • Rate limits. Per-key hourly and daily ceilings. Responses carry X-RateLimit-Limit and X-RateLimit-Remaining; exceeding one returns 429. Tell us your expected volume and we will set the ceiling to match.
  • Revocation. A key can be deactivated at any time; calls with it stop immediately and return 403.

Deed types

9 instruments, each rendered from its own template. The vesting column is worth reading closely: two instruments state their vesting on their own face, and for those the API rejects a supplied value rather than ignoring it.

deed_typeInstrumentgrantee.vestingAlso required
grant_deedGrant Deedrequired
quitclaim_deedQuitclaim Deed
A quitclaim conveys whatever interest the grantor holds, so vesting is optional.
optional
interspousal_transferInterspousal Transfer Deedrequired
warranty_deedWarranty Deedrequired
tax_deedTax Deedrequired
grant_deed_jtGrant Deed — Joint Tenancy
The instrument states joint tenancy on its face. Send grantee.vesting and the request is rejected — choosing this form is the vesting decision.
fixed by the instrument
grant_deed_cp_rosGrant Deed — Community Property with Right of Survivorship
Vesting is fixed by the instrument. Send grantee.vesting and the request is rejected.
fixed by the instrument
grant_deed_corpGrant Deed — Corporate Grantor
The deed recites the state under whose laws the corporation is organized.
required
grantor.entity.entity_state
grant_deed_partnershipGrant Deed — Partnership Grantor
The deed recites the partnership type and the state under whose laws it is organized.
required
grantor.entity.entity_state
grantor.entity.partnership_type

What the API will not do

Worth reading before you integrate, and worth handing to your counsel.

It will not decide legal choices for you

Vesting, the transfer-tax basis, whether an exemption applies, which instrument fits the transaction — these arrive as facts you send, and they are recorded as your declarations. DeedPro formats and records; it does not choose. Nothing the API returns is legal advice, and no response asserts that a document is valid, effective, or ready to record.

It will not quietly ignore an input it cannot use

Send a vesting clause to a joint-tenancy deed and you get a 422 naming the conflict — not a 200 and a document that disregarded it. If your input did not shape the instrument, you hear about it.

Some instruments require a human flow by design

v1 covers the deed family only. Affidavits and declarations — Affidavit of Death of Joint Tenant, of Trustee, of Spouse, Homestead Declaration, Certification of Trust, TOD Revocation, Statutory POA — carry execution-act machinery: statements sworn under jurat, initial lines, checkbox elections. Their whole premise is a human hand at the moment of execution, and a machine-to-machine call has no hand. Those instruments stay in the DeedPro app, where a person makes the elections and signs. This is a deliberate boundary, not a gap in the roadmap.

California only

Templates are measured to California county recorder requirements. property.state must be CA.

Recorded pages carry no verification chrome

Every API deed includes a blank California acknowledgment page for the notary to complete. That page is part of the deed chassis, not an option. The PDF does not print a QR code, verification URL, or DeedPro document ID; verification stays in the API response.

Idempotency & retries

A deed is a legal instrument, and a retried request must not produce a second one. Send an Idempotency-Key header — your order or file number works well — and a repeat with the same key returns the original draft. If that draft was rejected or expired, you get that record back. A replay does not resurrect a rejected body as a new draft.

Idempotency-Key: order-48219

Keys are scoped to your API key. PDF rendering happens server-side, so allow a generous timeout (60s) and retry with the same idempotency key rather than a fresh one.

Errors

Errors carry a stable code and a message meant to be actionable.

{
  "detail": {
    "code": "RATE_LIMITED",
    "message": "Hourly rate limit exceeded"
  }
}
StatusCodeWhat it means
401UNAUTHORIZEDMissing, malformed, or unrecognized key.
403FORBIDDENThe key exists but has been deactivated.
404NOT_FOUNDNo deed with that id belongs to your key. Deeds are scoped per key.
409CONFIRMATION_REQUIREDThe PDF was requested before the named approver confirmed the rendered deed. Deliver the confirmation URL; download after approval.
422VALIDATION_ERRORA required fact is missing, or an input conflicts with the instrument — a vesting clause sent to a fixed-vesting deed, or an entity deed without its organizing state. The message names the field.
429RATE_LIMITEDHourly or daily ceiling reached. Check the rate-limit headers.
500INTERNAL_ERRORSomething failed on our side. No deed was stored — retry with the same idempotency key.

Public verification

GET /verify/{document_id} needs no API key so a person holding the ID can check it. A valid response contains only the document ID, deed type, status, and creation time. It does not publish the property address, APN, or party names.

Public verification is limited to 60 attempts per client address per hour. A 429 response includes Retry-After and rate-limit headers.

Transfer tax

POST /transfer-tax/calculate returns a county and city breakdown for a value and location. It is a convenience for populating your own declaration — the amount that prints on the deed is the one you send in transfer_tax.

  • County rate: $1.10 per $1,000 (R&T §11911).
  • City rates apply only to cities that levy their own documentary transfer tax. A city that levies none is reported as levying none — the endpoint does not apply a generic rate.
  • City rates are approximations of tiered municipal schedules. Verify against the current schedule for the recording jurisdiction; the response carries this caveat alongside the number.

Declaration fields

transfer_tax.basis is the basis you direct the deed to print: full_value or less_liens. DeedPro does not infer or verify that legal choice. When using the calculator, send the lien amount in its numeric less_liens field and send the resulting amount in your deed request.

transfer_tax.exempt_code is a free-form string. The API does not maintain or validate against a statutory exemption-code list. Send a code only after your professional has determined the applicable wording.

Versioning & changelog

The current version is v1, covering the deed family. Additive changes — new deed types, new optional fields, new response keys — ship within v1 without notice. A later breaking change would ship under a new path (/api/v2).

Model 2 cutover, 2026-08-27. This broke v1 on purpose. There were no live integrators, keys were still manual, and keeping a generate-without-human path would have been a control that looked like a choice and did not enforce the ruling. The old promise — POST and receive a stored PDF — is dead, not deprecated.

2026-08-27 · v1 Model 2 cutover POST /api/v1/deeds returns a draft and a confirmation URL. A stored PDF exists only after approval.approver.name and approver.role are required. Reject-with-reason; a replayed Idempotency-Key does not resurrect a rejected draft.
2026-08 · v19 deed types (grant, quitclaim, interspousal, warranty, tax, joint tenancy, community property with right of survivorship, corporate and partnership grantors). Idempotency keys. Per-key rate limits and usage reporting. Public document verification.

Request API access

Three fields, no account needed. Tell us what you’re integrating and we’ll get in touch — we issue keys after a short conversation.

Already have a DeedPro account? Use the full form — it asks a few more questions about volume and timeline.