Skip to content

Asking Prok about an opportunity

POST /api/v1/opportunities/{id}/ask answers a question about one solicitation. It reads rather than writes, so it needs opportunities:read — the same scope as listing and reading opportunities.

Terminal window
curl -X POST "https://app.prokure.ca/api/v1/opportunities/$ID/ask" \
-H "Authorization: Bearer $PROKURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"question": "What is mandatory to bid on this?"}'
{
"answer": "Bidders must hold a valid security clearance, deliver to two sites, and return a signed bid form before the closing date. The notice does not state a bonding requirement.",
"model": "claude-haiku-4-5-20251001"
}

answer is plain text: no markdown, no headings, no formatting to strip. model names the model that composed it, so an integration can record which one answered. Treat that value as informational: it changes whenever Prokure changes models, and it is not part of the contract.

Prok answers from the material it already holds on that opportunity, and from nothing else:

  • the notice’s own facts — title, issuing body, end user, closing date, categories, geography, notice type, reference and solicitation numbers, and the published contact;
  • the notice description;
  • an excerpt of the solicitation document, when Prokure retrieved one;
  • Prok’s extraction of the solicitation;
  • Prok’s verdict — the score, whether you are eligible, the written explanation, and what the score was computed from;
  • the partner product that matched, when one did;
  • your company profile.

Two consequences worth designing for. When the answer is not in that material, Prok says so and points you at the posting rather than inventing a figure, a date or a certification. And when no solicitation document had been retrieved — so the notice metadata is all there was — Prok says the full document was not available wherever that bears on the answer, rather than answering as though it had read one.

The route is stateless: nothing is remembered between calls, so a follow-up question has to carry the exchange it follows in history.

Terminal window
curl -X POST "https://app.prokure.ca/api/v1/opportunities/$ID/ask" \
-H "Authorization: Bearer $PROKURE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"question": "Which of those do we already hold?",
"history": [
{ "role": "user", "text": "What is mandatory to bid on this?" },
{ "role": "assistant", "text": "Bidders must hold a valid security clearance..." }
]
}'

history is oldest first, and it must alternate — a user turn, then an assistant turn, then a user turn — starting with a user turn. At most 10 turns are accepted, each of 1–4000 characters, and only the most recent six reach the model. Send whole exchanges: a question whose answer failed should be dropped along with the answer rather than left in as an unpaired turn.

A history in any other order is rejected before the question is ever asked:

{
"error": "validation_failed",
"message": "history must alternate user and assistant turns, starting with a user turn",
"requestId": "req-b"
}

question itself runs 1–1000 characters. An opportunity that is not yours returns 404 not_found, a credential without opportunities:read returns 403 insufficient_scope, and a failure composing the answer returns 500 internal.

The route allows 20 requests per 60 seconds, counted per client IP like every other limit. It is built for a person asking follow-up questions, not for sweeping a page of opportunities.

The Ask AI button on an opportunity card calls this endpoint, with the panel’s transcript as history. An answer read in the portal and one your integration receives are grounded in the same material under the same rules, so neither sees anything the other cannot.