Prince Edward Island APIprinceedwardisland.ca ↗
Search PEI government tenders and retrieve full procurement postings including contacts, dates, documents, and award winners via a simple REST API.
What is the Prince Edward Island API?
The PEI Tenders API covers two endpoints that expose the Government of Prince Edward Island's procurement database, returning searchable tender summaries and full posting detail across provincial departments, Crown corporations, and municipalities. Use search_tenders to filter by keyword, organization, category, status, or publication year, and get_tender to pull a complete record including up to 15 structured response fields: description, documents, award winners, dates, and more.
curl -X GET 'https://api.parse.bot/scraper/719b3c5a-8c4b-4996-a620-6c1ab1c29450/search_tenders?status=Open' \ -H 'X-API-Key: $PARSE_API_KEY'
Typed, relational, agent-ready
A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.
- Fully typed · autocompletes
- Objects link to objects
- Typed errors & pagination
Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:
uv add parse-sdk uv run parse init uv run parse add --marketplace princeedwardisland-ca-api
uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.
"""Walkthrough: PEI Tenders SDK — search, drill into detail, handle not-found."""
from parse_apis.princeedwardisland_ca_api import (
PEITenders,
TenderStatus,
TenderCategory,
TenderNotFound,
)
client = PEITenders()
# Search open construction tenders, capped at 5 results.
for summary in client.tender_summaries.search(
status=TenderStatus.OPEN,
category=TenderCategory.CONSTRUCTION,
limit=5,
):
print(summary.title, "|", summary.organization, "|", summary.closing_date)
# Drill into the first open tender to read full procurement details.
first = client.tender_summaries.search(
status=TenderStatus.OPEN,
limit=1,
).first()
if first is not None:
detail = first.details()
print(detail.title)
print("Solicitation:", detail.solicitation_number)
print("Method:", detail.procurement_method)
print("Estimated value:", detail.estimated_value)
if detail.procurement_contact is not None:
print("Contact:", detail.procurement_contact.name, detail.procurement_contact.email)
for doc in detail.documents:
print(" Doc:", doc.group, "-", doc.name, doc.url)
for winner in detail.winners:
print(" Winner:", winner.name, winner.amount, winner.date)
# Point lookup by a known tender_id, with not-found handling.
try:
tender = client.tenders.get(tender_id=first.tender_id) if first else None
except TenderNotFound:
print("Tender no longer available")
print("exercised: tender_summaries.search / details / tenders.get")
Searches the PEI tender listing and returns one page of tender summaries (one row per tender posting) with the site's total match count. Rows are ordered as the site lists them: newest publication date first. Filters combine with AND: keyword matches title, description or solicitation number; category, status and organization are the site's dropdown values; publication_year restricts to tenders published in that calendar year. Status defaults to open tenders; pass status=all to include open, expired and cancelled tenders. Pagination is caller-controlled through page (1-based) and page_size (the site accepts only 5, 10, 20, 25 or 50); has_more is true while page * page_size is below total. A search with no matches returns an empty tenders array with total 0. Each row carries tender_id, which get_tender consumes unchanged, and external_url, the public posting page for that tender on princeedwardisland.ca. By default one upstream request per call. With include_details=true, each row additionally carries details (the same object get_tender returns for that tender, minus the repeated tender_id) and details_error (null on success, otherwise a short message); the response also carries details_failed, the number of rows whose detail lookup failed. This costs one extra upstream request per row (up to page_size, so at most 50 extra requests) and proportionally longer latency.
| Param | Type | Description |
|---|---|---|
| page | integer | 1-based page number of the result set. |
| status | string | Tender status filter. The special value all removes the status filter and returns tenders of every status. |
| keyword | string | Free-text keyword matched against the tender title, description or solicitation number (e.g. a solicitation number such as PEIG-5950, or a word such as roof). Omitted = no keyword filter. |
| category | string | Tender category from the site's Category dropdown. Omitted = all categories. An unrecognized value is forwarded to the site and yields an empty result. |
| page_size | integer | Rows per page. The site accepts only 5, 10, 20, 25 or 50; any other value is rejected with a validation error. |
| organization | string | Issuing organization exactly as it appears in the site's Organization dropdown and in the organization field of results (e.g. City of Charlottetown, Transportation and Infrastructure, Health PEI). Omitted = all organizations. The site's list has roughly 110 organizations; an unrecognized value yields an empty result. |
| include_details | boolean | When true, each row also carries details (the full posting as returned by get_tender) and details_error, and the response carries details_failed. Adds one upstream request per returned row. |
| publication_year | string | Four-digit calendar year the tender was published (the site offers 2019 through the current year). Omitted = all years. |
{
"type": "object",
"fields": {
"page": "integer: the page returned",
"total": "integer: total number of tenders matching the filters across all pages, as reported by the site",
"tenders": "array of tender summary rows: tender_id (numeric string, input for get_tender), solicitation_number, title, organization, publication_date (YYYY-MM-DD), closing_date (YYYY-MM-DD h:mmAM/PM, local PEI time), external_url (public posting page URL on princeedwardisland.ca); with include_details=true also details (object with the same fields as get_tender except tender_id, or null when the lookup failed) and details_error (null or a short failure message)",
"has_more": "boolean: true when further pages exist",
"page_size": "integer: rows per page applied",
"details_failed": "integer, present only with include_details=true: number of rows whose detail lookup failed (their details is null)"
},
"sample": {
"data": {
"page": 1,
"total": 23,
"tenders": [
{
"title": "Snow Removal",
"details": {
"title": "Snow Removal",
"winners": [],
"documents": [
{
"url": "https://tenders.princeedwardisland.ca/sites/tenders/files/tenders/Snow%20Removal%20Tender%20Ad%202026-2027.pdf",
"name": "Snow Removal Tender Ad 2026-2027.pdf",
"group": "Support"
}
],
"notice_type": "Not Applicable",
"closing_date": "2026-10-15 2:00PM",
"organization": "Finance PEI",
"amendment_date": "2026-09-22",
"estimated_value": "Not Available",
"description_html": "<p><span lang=\"EN-CA\">Snow Removal</span></p>",
"publication_date": "2026-09-22",
"trade_agreements": "None",
"contract_duration": null,
"contract_end_date": "2027-04-30",
"procurement_method": "Competitive – Open Bidding",
"contract_start_date": "2026-11-01",
"procurement_contact": {
"name": "[name]",
"email": "[email]",
"phone": "[phone]"
},
"solicitation_number": "FPEI-2026-05",
"organization_contact": {
"name": "",
"organization": "Finance PEI"
},
"commodity_description": "Snow Removal"
},
"tender_id": "18704",
"closing_date": "2026-10-15 2:00PM",
"external_url": "https://www.princeedwardisland.ca/en/feature/search-for-tenders-and-procurement-opportunities/#/service/Tenders/TenderView;tender_id=18704",
"organization": "Finance PEI",
"details_error": null,
"publication_date": "2026-09-22",
"solicitation_number": "FPEI-2026-05"
}
],
"has_more": true,
"page_size": 5,
"details_failed": 0
},
"status": "success"
}
}About the Prince Edward Island API
What the API Returns
The search_tenders endpoint queries the PEI tender listing and returns paginated summaries ordered by newest publication date first. Each row in the tenders array includes a tender_id (used as input to get_tender), solicitation_number, title, organization, and publication metadata. The total field gives the site-reported count of all matching records, and has_more indicates whether additional pages exist. Supported page_size values are 5, 10, 20, 25, or 50 — any other value returns a validation error.
Filtering Options
Filters on search_tenders combine with AND logic. The keyword parameter matches against tender title, description, or solicitation number. The status parameter accepts a specific status value or the special string all to remove the filter entirely. organization must match exactly as it appears in the site's Organization dropdown. publication_year accepts a four-digit year from 2019 through the current year. The category parameter maps to the site's Category dropdown; unrecognized values are forwarded as-is.
Full Tender Detail
Passing a tender_id to get_tender returns the complete posting. Fields include description_html (the posting body as HTML), notice_type (e.g., Request for Quotation), closing_date in PEI local time, amendment_date, estimated_value as shown by the site (often "Not Available"), and contract start/end dates. The documents array contains {group, name, url} entries for attached files. The winners array returns {name, date, amount, address} rows when award data has been recorded — it is empty for open or unadjudicated tenders.
The Prince Edward Island API is a managed, monitored endpoint for princeedwardisland.ca — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when princeedwardisland.ca changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.
This isn't an official princeedwardisland.ca API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.
Will this API break when the source site changes?+
Is this an official API from the source site?+
Can I fix or extend this API myself if I need a new endpoint or field?+
What happens if I call an endpoint that has an issue?+
- Monitor new PEI government contract opportunities by polling
search_tendersfiltered bycategoryandstatus. - Build a vendor alert system that detects tenders from a specific
organizationpublished in the currentpublication_year. - Aggregate awarded contract values from
winners[*].amountto analyze government spending patterns by department. - Index full tender text via
description_htmlfromget_tenderfor keyword-searchable procurement research tools. - Track amendment activity by comparing
amendment_dateacross repeated fetches of the sametender_id. - Compile a list of all active tenders in a category to benchmark competitor bid activity or market demand.
- Extract
documents[*].urllinks fromget_tenderto automate downloading of tender specification files.
| Tier | Price | Credits/month | Rate limit |
|---|---|---|---|
| Free | $0/mo | 200 | 5 req/min |
| Hobby | $30/mo | 1,000 | 20 req/min |
| Developer | $100/mo | 5,000 | 100 req/min |
| Team | $300/mo | 20,000 | 300 req/min |
| Company | $1,000/mo | 100,000 | 500 req/min |
Each endpoint has a fixed posted price per successful call — most fall between 1 and 10 credits — shown on this API's page before you run it. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.
Does princeedwardisland.ca have an official developer API for its tender data?+
What does the `winners` field in `get_tender` contain, and when is it empty?+
winners array contains zero or more objects with name, date, amount, and address fields, populated only when the PEI site has recorded award information for that tender. For open, cancelled, or unadjudicated tenders the array is empty. There is no separate endpoint to query winners in bulk; you retrieve them per-tender via get_tender.Can I retrieve tenders published before 2019?+
publication_year filter on search_tenders supports 2019 through the current year, matching the range offered by the source site. You can fork this API on Parse and revise it to add an endpoint targeting historical tender archives if the source exposes them.Are tenders from organizations outside the provincial government included?+
organization parameter controls.Does the API expose bidder contact details or submission instructions beyond what is in the posting?+
get_tender returns contacts embedded within description_html and structured fields like closing_date, documents, and organization, but does not return a parsed contacts array as a dedicated field. You can fork this API on Parse and revise it to extract and surface contact fields from the HTML if your application requires structured contact data.