Projects
Matchmaking
Find funding partners that may fit a business’s funding goal.
Read as MarkdownComplete a Project's Criteria, calculate funding recommendations, and create a Deal for a chosen recipient. Start with an existing Project; to create a business and its client-visible funding request first, follow Find funding matches.
A Project can stay private to your Portal; client matchmaking access is not required for that workflow. If you add a client funding request, it shares the Project's Criteria and matching work. Matching produces recommendations; creating and submitting a Deal are later steps.
Before you start
Use a server-side API key with access to this Project and projects:read, project_criteria:write, and project_matching:write. Creating a Deal additionally needs deals:write. The Deal guide explains how to complete and submit the application with deal_applications:write.
The receiving Portal must have matchmaking enabled and published recipient Criteria available to compare against. If the project has a linked client funding request, the business must still have an active relationship with your Portal.
Every request includes Authorization: Bearer <API_KEY>. JSON writes use Content-Type: application/json. Give each mutation its own Idempotency-Key and retain the key and body for retries. Examples use illustrative IDs and abbreviated responses. Replace them with the IDs and URLs your API returns; Get started provides the API origin.
1. Read the questions and current answers
Read the Project to get its business ID and application links:
GET /v1/projects/proj_example/
Response · selected fields
{
"id": "proj_example",
"business": { "id": "biz_example" },
"contract_url": "https://YOUR_API_HOST/v1/projects/proj_example/matchmaking-application/contract/",
"answers_url": "https://YOUR_API_HOST/v1/projects/proj_example/matchmaking-application/answers/"
}
Save business.id for Deal creation, then follow the two application URLs:
GET /v1/projects/proj_example/matchmaking-application/contract/
GET /v1/projects/proj_example/matchmaking-application/answers/
The contract describes each question's exact block_key, field_key, value type, and available operations. A question might look like this within the returned questions array:
{
"label": "Requested funding",
"block_key": "<amount-block-key>",
"fields": [
{
"field_key": "<amount-field-key>",
"value_schema": { "type": "string" }
}
]
}
These keys illustrate the structure; they are not universal field names. Use your contract's keys and constraints. Complete the relevant business and funding information, including conditional questions and repeated entries.
Save the answers response's ETag header for the next write. Its answers_revision identifies the version you reviewed. Answers are paginated: follow next_cursor until it is null when reviewing the full application.
2. Save or edit Criteria
Send only the values you want to change. This example assumes the contract accepts the amount as a string.
PATCH /v1/projects/proj_example/matchmaking-application/answers/
If-Match: <ETag from the answers GET>
Idempotency-Key: equipment-criteria-1
{
"updates": [
{
"block_key": "<amount-block-key>",
"field_key": "<amount-field-key>",
"value": "250000"
}
]
}
Response · 200, selected fields
{
"answers_revision": 2,
"updated_answers": [
{
"block_key": "<amount-block-key>",
"field_key": "<amount-field-key>",
"value": "250000"
}
]
}
The response confirms the saved values and includes the new ETag. Use it for your next edit; you do not need another read just to confirm the write. To correct the amount later, send the corrected value with the latest ETag and a new key. On 412, read and reconcile concurrent edits before sending another update.
For rows such as owners or assets, use the contract's repeatable_items_url and retain each returned item_id. A row's position is not its identity.
3. Attach supporting documents
Choose a file question from the contract. Request an upload using operations.file_uploads_url:
POST <operations.file_uploads_url>
{
"block_key": "<financial-statements-block-key>"
}
For a file inside a repeated row, also send its item_id.
Response · selected fields
{
"id": "upl_example",
"upload_url": "https://YOUR_API_HOST/v1/projects/proj_example/matchmaking-application/answers/files/upl_example/",
"multipart_field": "file",
"max_size_bytes": 26214400
}
Follow the returned URL and size limit; do not construct the URL from this example. The upload URL expires at the response's expires_at. Upload the file bytes as multipart, with the latest answers ETag:
curl -X POST "$UPLOAD_URL" \
-H "Authorization: Bearer $LEVR_API_KEY" \
-H "If-Match: $ANSWERS_ETAG" \
-H "Idempotency-Key: equipment-statements-1" \
-F "file=@financial-statements.pdf"
Let your HTTP client set the multipart boundary. A successful upload returns the attached file, the new answers_revision, and its ETag. Keep that revision for matching. Use operations.files_url when you need to list attachments; file bytes do not go in the answer JSON.
If the upload response is lost, retry the same URL with the same key, filename, bytes, and original If-Match. Requesting a new upload URL starts a separate transfer and can attach a duplicate. If an unused URL expires, request a new one.
4. Start matching
Review the saved Criteria, then send their current answers_revision:
POST /v1/projects/proj_example/matchmaking-runs/
Idempotency-Key: equipment-matching-1
{
"expected_answers_revision": 3
}
Response · 202, selected fields
{
"id": "pmrun_example",
"status": "queued",
"answers_revision": 3,
"url": "https://YOUR_API_HOST/v1/project-matchmaking-runs/pmrun_example/",
"results_url": "https://YOUR_API_HOST/v1/project-matchmaking-runs/pmrun_example/results/"
}
Save the run ID and URLs before continuing. Retry a lost response with the same key and body to recover the same run. A Project can have only one active calculation; do not start another while your saved run is in progress.
5. Wait for results
Poll the returned url while the status is queued or running. Space requests apart and respect rate-limit responses.
GET /v1/project-matchmaking-runs/pmrun_example/
Response · selected fields
{
"id": "pmrun_example",
"status": "completed",
"is_stale": false,
"impactful_missing_fields": [],
"results_url": "https://YOUR_API_HOST/v1/project-matchmaking-runs/pmrun_example/results/"
}
Alternatively, configure a Webhook Destination for project_matchmaking_run.status_changed before starting the run. Follow Webhook setup and signature verification. This event reports settled statuses: completed, partially_complete, failed, or cancelled. It does not report every progress update.
Webhook · selected fields
{
"type": "project_matchmaking_run.status_changed",
"data": {
"object": {
"id": "pmrun_example",
"project": "proj_example",
"status": "completed"
}
}
}
Verify the signature, deduplicate by the event ID, and fetch the run for its current state. Keep polling as a recovery path. For partially_complete, review the available results and unresolved failures before using them. A failed or cancelled run is not a completed assessment.
Stop or resume a calculation
To stop an active calculation, POST to /v1/project-matchmaking-runs/{run_id}/cancel/ with a new Idempotency-Key and an empty JSON body ({}). Poll the run to confirm its status. Cancellation preserves any results that have already completed.
To resume unresolved work on a failed or cancelled run, POST to /v1/project-matchmaking-runs/{run_id}/retry/ with {} and a new Idempotency-Key. The response is 202; keep polling the same run. Retry uses that run's original Criteria and recipient snapshot. If you changed the Criteria, start a new calculation after the old one settles instead.
A second calculation while one is active is rejected. A shortlist write is also rejected if the recipient's package version differs from the version you reviewed. Read the current run or results and review the change before trying again.
Use webhooks or REST polling for integration updates. The Portal handles its own websocket connection.
6. Review recommendations
Fetch results_url. When next_cursor is non-null, pass its value as the next request's cursor query parameter, preserving the same filters and page size. Continue until it is null.
GET /v1/project-matchmaking-runs/pmrun_example/results/
Response · selected fields
{
"items": [
{
"id": "pmres_example",
"recipient": { "id": "northstar-finance", "label": "Northstar Finance" },
"classification": "match",
"package_version": 2,
"availability": "available",
"is_selected": false
}
],
"next_cursor": null
}
Review each result's evaluation and availability, including missing information and uncertainty. Classifications include match, partial_fit, insufficient_information, not_a_match, and not_assessed. An empty list does not establish a suitable match. If the run is stale, review the changed Criteria and start a new run after the current run settles.
7. Save a shortlist
Read the selections for this run and save that response's ETag. Selections have their own revision; do not reuse the answers ETag.
GET /v1/projects/proj_example/matchmaking-selections/?run_id=pmrun_example
Then add the reviewed result, using its id and package_version:
PATCH /v1/projects/proj_example/matchmaking-selections/
If-Match: <ETag from the selections GET>
Idempotency-Key: equipment-shortlist-1
{
"run_id": "pmrun_example",
"additions": [
{
"match_result_id": "pmres_example",
"expected_package_version": 2
}
]
}
The response contains the updated selections and revision. Saving a selection records a shortlist choice; it does not create a Deal. This step is optional if your integration goes straight from a reviewed result to Deal creation.
8. Create and submit a Deal
For a chosen, available recipient, pass the result's recipient.id to Deal creation. The match result ID used for selections is a different identifier.
POST /v1/deals/
Idempotency-Key: equipment-northstar-deal-1
{
"business_id": "biz_example",
"project_id": "proj_example",
"recipient_id": "northstar-finance"
}
Create one Deal per intended recipient and save each response. Then follow Complete and submit a Deal to read its application, fill any remaining answers, upload required documents, check readiness, and submit. A match does not establish approval or funding.
In the Portal, open the Project to review Criteria, results, and selections; open the created Deal for its application and submission state. For MCP integrations, use the Matchmaking tools and discover their live contracts rather than translating REST paths into tool names.
