proposals — full tool reference
Editais/RFPs knowledge and assembly: company registry, document vault, answer library, opportunities, submissions.
| Endpoint | https://proposals.mcp.devfellowship.com/mcp |
| Package | packages/dfl-mcp-proposals |
| Tools | 21 |
| Tool | Description |
|---|---|
list_companies | List the companies that can apply to an opportunity — own entities (Revera/Itera/devfellowship) and external partners (e.g. B42). Filter by relationship (own|partner) or search legal_name / trade_name / cnpj. |
get_company | Get one company by id, optionally with its fiscal-year history (revenue/headcount) and contacts (accountant/legal/admin/partner). Note: the underlying vault documents are admin-gated and NOT returned here — use list_expiring_documents / upload_company_doc for the vault. |
upsert_company | Create a new company, or update an existing one when id is given. relationship (own|partner) is required when creating. Partners (e.g. B42) keep business_unit_id NULL; own entities may point at the canonical public.business_units roster (soft reference, app-enforced). |
upload_company_doc | Upload a SMALL document into the company vault by sending its bytes inline: base64 file_content is POSTed to the upload-file edge function as PRIVATE (public.media row, folder proposals/<company_id>/) and a proposals.company_documents metadata row is recorded pointing at it. The read path is always share.devfellowship.com/<media_id> or an MCP link — never a raw S3 URL. ⚠️ The base64 body is capped by the server bodyLimit (~10 MB body ≈ ~7.5 MB file); for LARGER files, upload to the private bucket first (upload-file edge function, visibility=private) and use link_company_doc with the returned media_id/URL — no bytes travel through the MCP body. VAULT tables are admin-gated (iam.is_global_admin) — the caller must be a global admin. |
link_company_doc | The definitive large-file path: record a proposals.company_documents vault row that points at a file ALREADY uploaded to the private bucket — NO file bytes travel through the MCP body, so there is no size limit. Upload the file first via the upload-file edge function (visibility=private) — that returns a media_id + a stable https://media.devfellowship.com/<id> link — then pass that reference here. media_ref accepts a bare media UUID, a media.devfellowship.com/<id> or share.devfellowship.com/<id> URL, or a private/<id>-<name> storage path. Optionally pass supersedes to replace an existing vault row (e.g. swap a compressed stopgap for the byte-exact original): the old row is marked status=superseded + superseded_by=<new id>. The read path is always share.devfellowship.com/<media_id> — never a raw S3 URL. VAULT tables are admin-gated (iam.is_global_admin) — the caller must be a global admin. |
list_expiring_documents | The certificate-renewal radar: vault documents whose valid_until falls within within_days from today (already-expired included). Order by soonest expiry. VAULT is admin-gated (iam.is_global_admin) — a non-admin caller gets an empty list, not an error. |
search_answers | Search the reusable answer library by free text (question_canonical / short_answer / long_answer_md) and/or facet tags. This is the reuse entry point — find an approved answer, then reference it from a submission section (never copy-paste). Returns each answer with its tags. Every row also reports its BODY TYPE: answer_kind is “markdown” or “sheet”. A “sheet” answer keeps its content in the work.sheets row named by sheet_id, NOT in long_answer_md — open it on the engineering MCP with get_sheet, and use copy_sheet when a submission needs its own fillable copy. Free-text search reads the markdown columns only, so find a budget template by its question_canonical or its tags. |
create_answer | Add a reusable answer to the library. company_id NULL = ecosystem-shared (DFL narrative); set = company-specific fact. New answers default to status=“draft”. Optionally attach facet tags and link a translation (translation_of) to pair PT/EN. SHEETS: an answer may BE a spreadsheet instead of Markdown. Create the sheet first on the engineering MCP (create_sheet, or copy_sheet to fill a template for one submission), then attach it here by passing its id as sheet_id. The kind field is inferred from sheet_id, so you rarely set it by hand; pass sheet_id: null to detach the sheet and go back to Markdown. |
update_answer | Edit an answer in the library. Optionally records an append-only revision snapshot (record_revision) and/or fully replaces the answer’s facet tags (tags). Use set_answer_status for status transitions. SHEETS: an answer may BE a spreadsheet instead of Markdown. Create the sheet first on the engineering MCP (create_sheet, or copy_sheet to fill a template for one submission), then attach it here by passing its id as sheet_id. The kind field is inferred from sheet_id, so you rarely set it by hand; pass sheet_id: null to detach the sheet and go back to Markdown. A recorded revision snapshots sheet_id and answer_kind together with long_answer_md, so the history of a sheet answer stays complete. |
set_answer_status | Transition an answer through its governance lifecycle: draft → approved → stale → retired. Approving stamps last_reviewed_at=now. This is the governance lever that keeps the library from rotting into a wiki. |
list_stale_answers | The library review queue: answers explicitly marked status=“stale” PLUS answers whose expires_at falls within within_days from now (already-expired included). Retired answers are excluded. Order by soonest expiry. Each row reports answer_kind and sheet_id, so a spreadsheet answer up for review is visible as one: its content lives in work.sheets, not in long_answer_md. |
create_opportunity | Register an edital/RFP/public tender/incentive program/award as a structured object (deadlines, value, status). Optionally attach a funder by id, or by name (a funder row is created on the fly). Optionally point source_media_id at the notice PDF already in the vault. |
add_requirements | Bulk-add the extracted checklist for an opportunity: document/eligibility/content/form/budget requirements, each with provenance (source_excerpt + source_page) back to the notice PDF. document requirements should set required_document_type so eligibility_check can match them against a company vault. |
eligibility_check | Cross a company against an opportunity’s requirements and return a per-requirement verdict + gap list. document requirements are auto-checked against the company vault (present + current + not-expired); eligibility criteria like {“min_revenue”,“min_years”,“min_headcount”} are auto-checked against fiscal years / incorporation date; content/form/budget requirements are flagged manual_review. NOTE: the vault is admin-gated — a non-admin caller sees no documents, so every document requirement reports “missing”. |
list_opportunities | List editais/RFPs/public tenders/incentive programs/awards already registered in the pipeline. Filter by status, funder_id, or kind. This is the discovery entry point — check here before create_opportunity to avoid registering a duplicate. Each row includes its resolved funder (id/name/kind). |
search_opportunities | Text search over opportunities by title, notice_number (número do edital), or funder name — the dedup entry point before create_opportunity (find it first, don’t create it blind). Combine with status/kind/funder_id filters. Each row includes its resolved funder. |
create_submission | Open a submission = one company applying to one opportunity (the multi-company anchor). Seeds the applying company as “lead” in submission_companies. Pass consortium to add partner companies (e.g. B42 as consortium_member). plan_slug ties it to the plans-app authoring workspace. |
upsert_section | Create or update a section of a submission. answer_id is the reuse/provenance link into the answer library (ADR-3: reference, never copy-paste); content_md is the opportunity-tailored final text ADAPTED from that answer. Pass id to update an existing section, omit to create. SHEETS: a section may BE a spreadsheet instead of Markdown — a budget table, for instance. Fill a template for one submission on the engineering MCP with copy_sheet (or build a new one with create_sheet), then pass the new sheet id as sheet_id here. section_kind is inferred from sheet_id; pass sheet_id: null to detach the sheet and go back to Markdown. answer_id stays the provenance link either way. |
attach_document | The compliance join: record that a specific vault document (company_document_id) satisfies a submission — optionally tied to the exact requirement it fulfills (requirement_id). This is what builds the submission checklist. The team can see “cartão CNPJ: attached” here even without vault read access to open the file itself. |
submission_checklist | Assemble the full compliance + drafting state of a submission: every opportunity requirement with the documents attached to satisfy it (submission_documents), plus each submission section and its status. Readable at member tier (the checklist is team-visible even when the underlying vault files are admin-gated). Each section reports its BODY TYPE: section_kind is “markdown” or “sheet”, and a sheet section carries the work.sheets id in sheet_id — read or edit it on the engineering MCP (get_sheet / write_sheet_cells). sections_sheet counts them. |
harvest_answers | The loop that compounds: promote reusable text written in a past submission back into the answer library. For each qualifying section, create a draft library answer (question_canonical = section title, long_answer_md = section content, source = provenance to this submission) and back-link the section to the new answer via answer_id. By default only sections NOT already linked to a library answer are harvested. SHEETS: a section whose body is a spreadsheet (section_kind=“sheet”) qualifies on its sheet_id alone, with or without content_md, and the promoted answer carries the SAME sheet_id with answer_kind=“sheet”. The sheet itself is never copied — the library answer and the submission section point at one work.sheets row, so editing it later shows in both. |
list_companies
Section titled “list_companies”List Companies
List the companies that can apply to an opportunity — own entities (Revera/Itera/devfellowship) and external partners (e.g. B42). Filter by relationship (own|partner) or search legal_name / trade_name / cnpj.
| Parameter | Type | Required | Description |
|---|---|---|---|
relationship | enum | no | Filter by relationship: “own” (DFL entity) or “partner” (external, e.g. B42) One of: own, partner. |
search | string | no | Case-insensitive substring match on legal_name, trade_name, or cnpj |
limit | number | no | Max rows (default 50, max 100) |
offset | number | no | Rows to skip (pagination) |
get_company
Section titled “get_company”Get Company
Get one company by id, optionally with its fiscal-year history (revenue/headcount) and contacts (accountant/legal/admin/partner). Note: the underlying vault documents are admin-gated and NOT returned here — use list_expiring_documents / upload_company_doc for the vault.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | The UUID of the company |
include_related | boolean | no | Also return company_fiscal_years and company_contacts (default false) |
upsert_company
Section titled “upsert_company”Upsert Company
Create a new company, or update an existing one when id is given. relationship (own|partner) is required when creating. Partners (e.g. B42) keep business_unit_id NULL; own entities may point at the canonical public.business_units roster (soft reference, app-enforced).
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | no | UUID of an existing company to UPDATE. Omit to CREATE a new one. |
relationship | enum | no | own = DFL entity (Revera/Itera/devfellowship); partner = external. REQUIRED when creating. One of: own, partner. |
business_unit_id | string | no | Canonical public.business_units id for own entities; NULL for partners (soft reference, no FK). |
cnpj | string | no | Brazilian company tax id (unique across companies) |
legal_name | string | no | Razão social (legal name) |
trade_name | string | no | Nome fantasia (trade name) |
incorporated_at | string | no | Incorporation date (YYYY-MM-DD) |
legal_form | string | no | Natureza jurídica (legal form, e.g. LTDA) |
tax_regime | string | no | Regime tributário (e.g. Simples Nacional, Lucro Presumido) |
primary_cnae | string | no | Primary CNAE code |
share_capital | number | no | Capital social (numeric) |
state_registration | string | no | Inscrição estadual |
municipal_registration | string | no | Inscrição municipal |
fiscal_address | object | no | Fiscal address as a JSON object |
company_size | enum | no | Porte (legal size classification) One of: MEI, ME, EPP, other. |
extra | object | no | Catch-all JSON for fields not yet promoted to columns |
upload_company_doc
Section titled “upload_company_doc”Upload Company Document (Vault)
Upload a SMALL document into the company vault by sending its bytes inline: base64 file_content is POSTed to the upload-file edge function as PRIVATE (public.media row, folder proposals/<company_id>/) and a proposals.company_documents metadata row is recorded pointing at it. The read path is always share.devfellowship.com/<media_id> or an MCP link — never a raw S3 URL. ⚠️ The base64 body is capped by the server bodyLimit (~10 MB body ≈ ~7.5 MB file); for LARGER files, upload to the private bucket first (upload-file edge function, visibility=private) and use link_company_doc with the returned media_id/URL — no bytes travel through the MCP body. VAULT tables are admin-gated (iam.is_global_admin) — the caller must be a global admin.
| Parameter | Type | Required | Description |
|---|---|---|---|
company_id | string | yes | UUID of the company this document belongs to |
file_content | string | yes | Base64-encoded file content |
file_name | string | yes | File name with extension (e.g. “contrato_social.pdf”) |
mime_type | string | yes | MIME type (e.g. “application/pdf”) |
document_type | string | yes | Free-text type, e.g. contrato_social | cartao_cnpj | certidao_* | balanco | atestado |
label | string | no | Human label for the document |
notes | string | no | Freeform notes |
issued_at | string | no | Issue date (YYYY-MM-DD) |
valid_until | string | no | Validity/expiry date (YYYY-MM-DD) — drives the certificate-renewal radar |
link_company_doc
Section titled “link_company_doc”Link Pre-Uploaded Company Document (Vault)
The definitive large-file path: record a proposals.company_documents vault row that points at a file ALREADY uploaded to the private bucket — NO file bytes travel through the MCP body, so there is no size limit. Upload the file first via the upload-file edge function (visibility=private) — that returns a media_id + a stable https://media.devfellowship.com/<id> link — then pass that reference here. media_ref accepts a bare media UUID, a media.devfellowship.com/<id> or share.devfellowship.com/<id> URL, or a private/<id>-<name> storage path. Optionally pass supersedes to replace an existing vault row (e.g. swap a compressed stopgap for the byte-exact original): the old row is marked status=superseded + superseded_by=<new id>. The read path is always share.devfellowship.com/<media_id> — never a raw S3 URL. VAULT tables are admin-gated (iam.is_global_admin) — the caller must be a global admin.
| Parameter | Type | Required | Description |
|---|---|---|---|
company_id | string | yes | UUID of the company this document belongs to |
media_ref | string | yes | Reference to an ALREADY-uploaded PRIVATE file: a media UUID, a media.devfellowship.com/<id> or share.devfellowship.com/<id> URL, or a private/<id>-<name> storage path. The file must have been uploaded via the upload-file edge function with visibility=private first. |
document_type | string | yes | Free-text type, e.g. contrato_social | cartao_cnpj | certidao_* | balanco | atestado |
label | string | no | Human label for the document |
notes | string | no | Freeform notes |
issued_at | string | no | Issue date (YYYY-MM-DD) |
valid_until | string | no | Validity/expiry date (YYYY-MM-DD) — drives the certificate-renewal radar |
supersedes | string | no | UUID of an existing proposals.company_documents row this document REPLACES. When set, that row is marked status=superseded and superseded_by=<new row id> after the new row is inserted (e.g. replacing a compressed stopgap with the byte-exact original). |
list_expiring_documents
Section titled “list_expiring_documents”List Expiring Documents
The certificate-renewal radar: vault documents whose valid_until falls within within_days from today (already-expired included). Order by soonest expiry. VAULT is admin-gated (iam.is_global_admin) — a non-admin caller gets an empty list, not an error.
| Parameter | Type | Required | Description |
|---|---|---|---|
within_days | number | no | Look-ahead window in days (default 30). Documents expiring within this many days (or already expired) are returned. |
company_id | string | no | Restrict to one company |
include_expired | boolean | no | Include already-expired documents (default true) |
search_answers
Section titled “search_answers”Search Answers (Library)
Search the reusable answer library by free text (question_canonical / short_answer / long_answer_md) and/or facet tags. This is the reuse entry point — find an approved answer, then reference it from a submission section (never copy-paste). Returns each answer with its tags. Every row also reports its BODY TYPE: answer_kind is “markdown” or “sheet”. A “sheet” answer keeps its content in the work.sheets row named by sheet_id, NOT in long_answer_md — open it on the engineering MCP with get_sheet, and use copy_sheet when a submission needs its own fillable copy. Free-text search reads the markdown columns only, so find a budget template by its question_canonical or its tags.
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | no | Case-insensitive substring across question_canonical, short_answer, long_answer_md |
tags | string[] | no | Only answers carrying ALL of these facet tags |
status | enum | no | Filter by governance status One of: draft, approved, stale, retired. |
language | enum | no | Filter by language One of: pt, en. |
company_id | string | no | Company-specific answers for this company id |
shared_only | boolean | no | Only ecosystem-shared answers (company_id IS NULL) |
limit | number | no | Max rows (default 50, max 100) |
offset | number | no | Rows to skip (pagination) |
create_answer
Section titled “create_answer”Create Answer
Add a reusable answer to the library. company_id NULL = ecosystem-shared (DFL narrative); set = company-specific fact. New answers default to status=“draft”. Optionally attach facet tags and link a translation (translation_of) to pair PT/EN. SHEETS: an answer may BE a spreadsheet instead of Markdown. Create the sheet first on the engineering MCP (create_sheet, or copy_sheet to fill a template for one submission), then attach it here by passing its id as sheet_id. The kind field is inferred from sheet_id, so you rarely set it by hand; pass sheet_id: null to detach the sheet and go back to Markdown.
| Parameter | Type | Required | Description |
|---|---|---|---|
question_canonical | string | yes | The canonical question/prompt this answer responds to |
short_answer | string | no | One/two-line summary |
long_answer_md | string | no | Full answer in Markdown |
language | enum | no | Language (default pt) One of: pt, en. |
company_id | string | no | Company id, or NULL/omit for ecosystem-shared |
translation_of | string | no | UUID of the source-language answer this one translates |
status | enum | no | Governance status (default draft) One of: draft, approved, stale, retired. |
expires_at | string | no | When this answer should be re-reviewed (ISO timestamp) |
tags | string[] | no | Facet tags (e.g. institutional, impact, safeguarding) |
source | object | no | Provenance JSON (plan slug, submission id, vault doc) |
sheet_id | string | no | UUID of a work.sheets spreadsheet that IS this answer (e.g. the “Orçamento detalhado” budget). Create it on the engineering MCP with create_sheet or copy_sheet first. Passing it sets answer_kind=“sheet” on its own. |
answer_kind | enum | no | Which body this answer carries (default markdown, inferred as “sheet” when sheet_id is given). “sheet” without a sheet_id is refused here, before the database CHECK. One of: markdown, sheet. |
update_answer
Section titled “update_answer”Update Answer
Edit an answer in the library. Optionally records an append-only revision snapshot (record_revision) and/or fully replaces the answer’s facet tags (tags). Use set_answer_status for status transitions. SHEETS: an answer may BE a spreadsheet instead of Markdown. Create the sheet first on the engineering MCP (create_sheet, or copy_sheet to fill a template for one submission), then attach it here by passing its id as sheet_id. The kind field is inferred from sheet_id, so you rarely set it by hand; pass sheet_id: null to detach the sheet and go back to Markdown. A recorded revision snapshots sheet_id and answer_kind together with long_answer_md, so the history of a sheet answer stays complete.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | UUID of the answer to update |
question_canonical | string | no | Update the canonical question |
short_answer | string | no | Update the short answer |
long_answer_md | string | no | Update the long answer (Markdown) |
language | enum | no | Update language One of: pt, en. |
company_id | string | no | Reassign company (NULL = ecosystem-shared) |
translation_of | string | no | Update the translation pairing |
expires_at | string | no | Update the re-review date |
source | object | no | Replace the provenance JSON |
tags | string[] | no | If provided, FULLY REPLACES the answer’s tags with this set |
record_revision | boolean | no | If true, append a row to answer_revisions capturing the new long_answer_md PLUS sheet_id and answer_kind (default false) |
sheet_id | string | no | Attach a work.sheets spreadsheet as this answer’s body (create it on the engineering MCP with create_sheet or copy_sheet first), or pass null to detach it and go back to Markdown. |
answer_kind | enum | no | Which body this answer carries. Inferred from sheet_id, so you rarely set it: “sheet” needs a sheet_id in this call or already on the row, and “markdown” alongside a new sheet_id is refused. One of: markdown, sheet. |
set_answer_status
Section titled “set_answer_status”Set Answer Status
Transition an answer through its governance lifecycle: draft → approved → stale → retired. Approving stamps last_reviewed_at=now. This is the governance lever that keeps the library from rotting into a wiki.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | yes | UUID of the answer |
status | enum | yes | New governance status One of: draft, approved, stale, retired. |
expires_at | string | no | Optionally (re)set the re-review date (ISO timestamp; null clears it) |
list_stale_answers
Section titled “list_stale_answers”List Stale Answers
The library review queue: answers explicitly marked status=“stale” PLUS answers whose expires_at falls within within_days from now (already-expired included). Retired answers are excluded. Order by soonest expiry. Each row reports answer_kind and sheet_id, so a spreadsheet answer up for review is visible as one: its content lives in work.sheets, not in long_answer_md.
| Parameter | Type | Required | Description |
|---|---|---|---|
within_days | number | no | Also include answers expiring within this many days (default 0 = only already-expired + status=stale) |
company_id | string | no | Restrict to one company |
limit | number | no | Max rows (default 100, max 200) |
create_opportunity
Section titled “create_opportunity”Create Opportunity
Register an edital/RFP/public tender/incentive program/award as a structured object (deadlines, value, status). Optionally attach a funder by id, or by name (a funder row is created on the fly). Optionally point source_media_id at the notice PDF already in the vault.
| Parameter | Type | Required | Description |
|---|---|---|---|
kind | enum | yes | grant_notice=edital, rfp, public_tender=licitação, incentive_program=incentivo, award=prêmio One of: grant_notice, rfp, public_tender, incentive_program, award. |
title | string | yes | Human title of the opportunity |
notice_number | string | no | Número do edital / notice number |
url | string | no | Public URL of the opportunity notice |
funder_id | string | no | UUID of an existing funder |
funder_name | string | no | If no funder_id, create a funder with this name and link it |
funder_kind | enum | no | Kind of the on-the-fly funder (only used with funder_name) One of: federal, state, municipal, multilateral, foundation, corporate. |
source_media_id | string | no | public.media id of the notice PDF in the vault |
published_at | string | no | Publication timestamp (ISO) |
questions_deadline | string | no | Clarification-questions deadline (ISO) |
submission_deadline | string | no | Submission deadline (ISO) |
total_value | number | no | Total value of the opportunity |
currency | string | no | Currency code (default BRL) |
status | enum | no | Pipeline status (default scouted) One of: scouted, analyzing, go, no_go, preparing, submitted, won, lost, cancelled. |
go_no_go_notes | string | no | Go/No-Go decision notes |
add_requirements
Section titled “add_requirements”Add Opportunity Requirements
Bulk-add the extracted checklist for an opportunity: document/eligibility/content/form/budget requirements, each with provenance (source_excerpt + source_page) back to the notice PDF. document requirements should set required_document_type so eligibility_check can match them against a company vault.
| Parameter | Type | Required | Description |
|---|---|---|---|
opportunity_id | string | yes | UUID of the opportunity |
requirements | object[] | yes | One or more requirements to insert |
eligibility_check
Section titled “eligibility_check”Eligibility Check
Cross a company against an opportunity’s requirements and return a per-requirement verdict + gap list. document requirements are auto-checked against the company vault (present + current + not-expired); eligibility criteria like {“min_revenue”,“min_years”,“min_headcount”} are auto-checked against fiscal years / incorporation date; content/form/budget requirements are flagged manual_review. NOTE: the vault is admin-gated — a non-admin caller sees no documents, so every document requirement reports “missing”.
| Parameter | Type | Required | Description |
|---|---|---|---|
opportunity_id | string | yes | UUID of the opportunity |
company_id | string | yes | UUID of the applying company |
list_opportunities
Section titled “list_opportunities”List Opportunities
List editais/RFPs/public tenders/incentive programs/awards already registered in the pipeline. Filter by status, funder_id, or kind. This is the discovery entry point — check here before create_opportunity to avoid registering a duplicate. Each row includes its resolved funder (id/name/kind).
| Parameter | Type | Required | Description |
|---|---|---|---|
status | enum | no | Filter by pipeline status One of: scouted, analyzing, go, no_go, preparing, submitted, won, lost, cancelled. |
funder_id | string | no | Filter by funder UUID |
kind | enum | no | Filter by opportunity kind (grant_notice=edital, public_tender=licitação, incentive_program=incentivo, award=prêmio) One of: grant_notice, rfp, public_tender, incentive_program, award. |
limit | number | no | Max rows (default 50, max 100) |
offset | number | no | Rows to skip (pagination) |
search_opportunities
Section titled “search_opportunities”Search Opportunities
Text search over opportunities by title, notice_number (número do edital), or funder name — the dedup entry point before create_opportunity (find it first, don’t create it blind). Combine with status/kind/funder_id filters. Each row includes its resolved funder.
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | no | Case-insensitive substring across title, notice_number, and funder name |
status | enum | no | Filter by pipeline status One of: scouted, analyzing, go, no_go, preparing, submitted, won, lost, cancelled. |
kind | enum | no | Filter by opportunity kind One of: grant_notice, rfp, public_tender, incentive_program, award. |
funder_id | string | no | Filter by funder UUID |
limit | number | no | Max rows (default 50, max 100) |
offset | number | no | Rows to skip (pagination) |
create_submission
Section titled “create_submission”Create Submission
Open a submission = one company applying to one opportunity (the multi-company anchor). Seeds the applying company as “lead” in submission_companies. Pass consortium to add partner companies (e.g. B42 as consortium_member). plan_slug ties it to the plans-app authoring workspace.
| Parameter | Type | Required | Description |
|---|---|---|---|
opportunity_id | string | yes | UUID of the opportunity |
company_id | string | yes | UUID of the primary applying company (recorded as lead) |
status | enum | no | Pipeline status (default draft) One of: draft, internal_review, submitted, clarifications, won, lost, withdrawn. |
plan_slug | string | no | plans-app plan slug used as the authoring workspace |
consortium | object[] | no | Additional consortium companies beyond the lead |
upsert_section
Section titled “upsert_section”Upsert Submission Section
Create or update a section of a submission. answer_id is the reuse/provenance link into the answer library (ADR-3: reference, never copy-paste); content_md is the opportunity-tailored final text ADAPTED from that answer. Pass id to update an existing section, omit to create. SHEETS: a section may BE a spreadsheet instead of Markdown — a budget table, for instance. Fill a template for one submission on the engineering MCP with copy_sheet (or build a new one with create_sheet), then pass the new sheet id as sheet_id here. section_kind is inferred from sheet_id; pass sheet_id: null to detach the sheet and go back to Markdown. answer_id stays the provenance link either way.
| Parameter | Type | Required | Description |
|---|---|---|---|
submission_id | string | yes | UUID of the submission |
id | string | no | UUID of an existing section to UPDATE (omit to CREATE) |
title | string | no | Section title |
sort_order | number | no | Ordering within the submission |
answer_id | string | no | Library answer this section reuses (provenance FK; also gives usage-tracking for free) |
content_md | string | no | The final, opportunity-adapted text (Markdown) |
status | enum | no | Section drafting status (default todo on create) One of: todo, drafted, reviewed, final. |
sheet_id | string | no | UUID of the work.sheets spreadsheet that IS this section (typically a copy_sheet of the opportunity budget template). Pass null to detach it. |
section_kind | enum | no | Which body this section carries (default markdown, inferred as “sheet” when sheet_id is given). “sheet” without a sheet_id is refused here, before the database CHECK. One of: markdown, sheet. |
attach_document
Section titled “attach_document”Attach Document to Submission
The compliance join: record that a specific vault document (company_document_id) satisfies a submission — optionally tied to the exact requirement it fulfills (requirement_id). This is what builds the submission checklist. The team can see “cartão CNPJ: attached” here even without vault read access to open the file itself.
| Parameter | Type | Required | Description |
|---|---|---|---|
submission_id | string | yes | UUID of the submission |
company_document_id | string | yes | UUID of the vault document (proposals.company_documents) that satisfies the requirement |
requirement_id | string | no | UUID of the opportunity_requirement this document fulfills (optional) |
status | enum | no | Attachment status (default pending) One of: pending, attached, needs_renewal. |
submission_checklist
Section titled “submission_checklist”Submission Checklist
Assemble the full compliance + drafting state of a submission: every opportunity requirement with the documents attached to satisfy it (submission_documents), plus each submission section and its status. Readable at member tier (the checklist is team-visible even when the underlying vault files are admin-gated). Each section reports its BODY TYPE: section_kind is “markdown” or “sheet”, and a sheet section carries the work.sheets id in sheet_id — read or edit it on the engineering MCP (get_sheet / write_sheet_cells). sections_sheet counts them.
| Parameter | Type | Required | Description |
|---|---|---|---|
submission_id | string | yes | UUID of the submission |
harvest_answers
Section titled “harvest_answers”Harvest Answers from Submission
The loop that compounds: promote reusable text written in a past submission back into the answer library. For each qualifying section, create a draft library answer (question_canonical = section title, long_answer_md = section content, source = provenance to this submission) and back-link the section to the new answer via answer_id. By default only sections NOT already linked to a library answer are harvested. SHEETS: a section whose body is a spreadsheet (section_kind=“sheet”) qualifies on its sheet_id alone, with or without content_md, and the promoted answer carries the SAME sheet_id with answer_kind=“sheet”. The sheet itself is never copied — the library answer and the submission section point at one work.sheets row, so editing it later shows in both.
| Parameter | Type | Required | Description |
|---|---|---|---|
submission_id | string | yes | UUID of the submission to harvest from |
section_ids | string[] | no | Restrict to these section UUIDs (default: all qualifying sections) |
only_unlinked | boolean | no | Only harvest sections with no answer_id yet (default true) |
company_scoped | boolean | no | If true, set the new answers’ company_id to the submission’s company (default false = ecosystem-shared) |