How to extract Quebec land registry index pages with the Fonciq API
Order an index, follow the job, read the rows as JSON or CSV. cURL, Python and Node examples.
An index of immovables is ordered, followed and read in three calls. Each filing in the index becomes a structured row (registration number, presentation date, nature, parties, cancellations as registered). The source PDF always comes back with the rows and is the authoritative record.
This page is an API guide. The overview of the Fonciq API, its operations and published limits is on fonciq.ca. The reference pages are in French.
Before you start
- An API key: see Premier appel (French). A
fq_test_key bills nothing. - Your organisation's own Registre foncier account, added in Studio › Développeurs. Without an account, no index can be ordered.
1. Order the index
POST /v1/registre/requests (scope registre:write) requires an Idempotency-Key header, kept for 24 hours. Retrying with the same key returns the same response.
curl -X POST https://api.fonciq.ca/v1/registre/requests \
-H "Authorization: Bearer $FONCIQ_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f1c0b2e-7a4d-4e1a-9c2b-0d5e6f7a8b9c" \
-d '{"lot":"1234567","documents":["index"]}'The response is 202:
{ "job_id": "job_01HZX", "account_id": "rfa_exemple" }2. Follow the job
The lookup is asynchronous. GET /v1/jobs/{id} (scope jobs:read) returns the status: queued, capacity_wait, running, succeeded or failed. capacity_wait means the account's daily cap is still holding the request back. The account's capacity is in the same response.
curl https://api.fonciq.ca/v1/jobs/job_01HZX \
-H "Authorization: Bearer $FONCIQ_API_KEY"Your key is limited to 60 requests per minute, so poll at a reasonable interval.
3. Read the rows
When the job is succeeded, the result is on result.index (one block per lot for a batch). It carries pdf_url, entries_url and entries. The same rows can be read directly with GET /v1/documents/{id}/entries, as JSON by default or CSV with format=csv. The source PDF is GET /v1/documents/{id}.
curl "https://api.fonciq.ca/v1/documents/42/entries?format=csv" \
-H "Authorization: Bearer $FONCIQ_API_KEY"Python
import os, time, uuid, requests
BASE = "https://api.fonciq.ca"
HEADERS = {"Authorization": f"Bearer {os.environ['FONCIQ_API_KEY']}"}
job = requests.post(
f"{BASE}/v1/registre/requests",
headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
json={"lot": "1234567", "documents": ["index"]},
).json()
while True:
state = requests.get(f"{BASE}/v1/jobs/{job['job_id']}", headers=HEADERS).json()
if state["status"] in ("succeeded", "failed"):
break
time.sleep(10)
if state["status"] == "succeeded":
for entry in state["result"]["index"]["entries"]:
print(entry["inscription"], entry["presentation_date"], entry["nature"])Node
const BASE = 'https://api.fonciq.ca';
const headers = { Authorization: `Bearer ${process.env.FONCIQ_API_KEY}` };
const created = await fetch(`${BASE}/v1/registre/requests`, {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID() },
body: JSON.stringify({ lot: '1234567', documents: ['index'] }),
}).then((r) => r.json());
let job;
do {
await new Promise((resolve) => setTimeout(resolve, 10_000));
job = await fetch(`${BASE}/v1/jobs/${created.job_id}`, { headers }).then((r) => r.json());
} while (!['succeeded', 'failed'].includes(job.status));
if (job.status === 'succeeded') {
for (const entry of job.result.index.entries) console.log(entry.inscription, entry.nature);
}What the rows contain
| Field | Content |
|---|---|
inscription | Registration number, spaces kept. |
presentation_date | Presentation date, YYYY-MM-DD. |
nature | Nature of the deed. Null when the column was not read. |
parties | { "role", "name" }, when the capacity is recognised. |
radiations | Cancellation mentions as registered: inscription, mention, raw_text. |
confidence | Always the literal unscored: the current parser does not produce a score. Leave it as unscored. |
source_document_id | Identifier of the source PDF. |
The PDF is the authoritative record. An empty entries list is an empty read: no row is invented to fill it, and rows with no date or no registration number are set aside.
Cost
Each index delivered costs CAD 0.99 excluding Registre fees. An index always consults the Registre, which charges CAD 1.50 to the account holder, so one consulted index costs CAD 2.49 in total. The Registre fee is not a Fonciq charge. A fq_test_ key bills nothing.
Common errors
| Code | Cause |
|---|---|
401 | Missing or revoked key, or missing the registre:write or jobs:read scope. |
400 | Invalid body, or a format other than csv on a rows read. |
409 | No Registre account: rf_account_required. |
404 | The document is not an index produced for your organisation. |
429 | More than 60 requests per minute (rate_limited): wait for Retry-After. |
Next
- Many lots at once: Bulk index search by CSV.
- Be told about a new filing: Land registry filing webhooks.