{"version":"3.4.1","purpose":"How AI agents should create quotes, jobs, and invoices in Forge so documents look professional — same standard as a well-written Jobber quote: clear title, clean line items, full scope under each line, correct price, short client note. Includes mandatory client/property resolve-or-create workflow.","philosophy":{"goal":"The client opens the quote and immediately understands: what work, where, what's included, and the total. No blank placeholders. No mystery step lines.","look_and_feel":"Match a good Jobber quote: professional header fields, one strong description column with title + scope underneath, qty, unit, unit price, line total, then subtotal / tax / total.","consistency":"Quotes, jobs, and invoices should use the same line-item shape so approving a quote and invoicing later stays clean.","agency":"Agents are expected to finish the work. If a client or property is missing, create it. Do not stop and ask the user to create records that tools can create."},"prerequisites":{"title":"Resolve client + property BEFORE creating a quote (mandatory)","why":"forge_create_quote requires a real clientId. Property names (Vibe Lofts) are often NOT the same as the client legal name (Killam Apartment REIT). Searching only the site nickname can return zero clients even when the client already exists.","steps":["1. Search for the client: forge_list_clients search='Killam' intendedName='Killam Apartment REIT' (full name). Partial ILIKE is only a candidate list — require match.kind==='match' or matchOk=true before reuse.","2. If zero STRONG matches, also search properties: forge_search types='properties' or forge_list_properties search='Vibe Lofts'. Property hits return clientId — still verify that client's name strongly matches the requested company before reuse.","3. If client still missing or only weak hits: forge_create_client with the exact requested name (e.g. 'Agent Kin Test', not a random 'Agent…' hit), billingEmail if known, notes that it was created by agent for quoting.","4. Resolve property: forge_list_properties clientId=<id> search='Vibe' (or site address). Prefer the property row that has a full serviceAddress.","5. If property missing: forge_create_property with clientId, label (site nickname), serviceAddress, serviceCity, serviceProvince.","6. ONLY THEN call forge_create_quote with clientId + propertyId + Jobber-style line items.","7. If the client or job site is not already on file, ask before creating it. Show closest matches and the name, phone, email, and address from the notes. After they say yes, call the playbook again with confirmCreateClient and, if the site is new, confirmCreateProperty. Do not create a half record named after the walk."],"search_tips":["Client name search is partial ILIKE — 'Killam' matches 'Killam Apartment REIT'. That is a CANDIDATE list only.","Strong match required to reuse: exact/near-exact name, or distinctive long token (Killam). Weak shared token (Agent Kin Test vs Agent Smoke) = CREATE new client.","Property label search is separate — 'Vibe' matches property 'Vibe Lofts' even when client is Killam.","If Jobber has a client/property, mirror that structure in Forge: one client company, many properties/sites under it.","Avoid creating duplicate clients for the SAME company. Do create a new client when the requested name is a different company that only shares a weak token.","Avoid duplicate empty properties. If two 'Vibe Lofts' exist, use the one with a real serviceAddress."],"real_world_example":{"request":"Create quotes for Killam / Vibe Lofts units 401, 301, 201 drywall repairs","correct_flow":["forge_list_clients search='Killam' → finds Killam Apartment REIT","forge_list_properties search='Vibe' (or clientId) → finds Vibe Lofts @ 10620 116 St NW","forge_create_quote x3 with clientId + propertyId + ONE line each (description + detail + total)"],"wrong_flow":["Search only for 'Vibe Lofts' as a client name → miss Killam → stop and ask user","Search 'Agent' → pick first hit 'Agent Smoke 122553' for request 'Agent Kin Test' (weak token) → misfile quote","Or invent 5 step line items per unit"]}},"quotes":{"preferred_shape":"one_lump_sum_line_item_for_single_job","rules":["Write a human title: '{Property or Unit} - {Work}' e.g. 'Vibe Lofts - Unit 401 Drywall Repairs'.","DEFAULT: ONE line item for a single job/scope. Put the TOTAL price on that line.","description = short client-facing line title (required). Shown as the bold main text on the quote PDF and app.","detail = full scope of work / specs (required for quality). Shown under the title in smaller text WITH line breaks. Put each work step on its own line starting with '• ' (bullet + space). Example: '• Demolition of existing threshold\n• Excavation for drainage swale\n• Dowel 10mm rebar\n• Place 30 MPa concrete\n• Finish to match grade'. NEVER join steps with ' - ' on one line — clients see a wall of text. Newlines are preserved on the public quote page and PDF.","qty = 1 and unit = 'each' or 'lump sum' for fixed-price jobs. Use real units (sqft, hrs, lf) only when pricing is unit-based.","Always set unitPrice (dollars) or unitPriceCents when the price is known. A $0 line is allowed as a TBD placeholder when other lines are priced (e.g. door mill quote still coming). Do not drop that line and do not refuse the quote.","Do NOT split one repair into many equal-priced step lines (replace / tape / prime / paint / baseboards as 5 × $243).","Only use multiple line items when the client should see distinct billable services (different units, materials allowance vs labour, alternate options, or truly separate scopes).","notes = short cover note to the client (validity, exclusions, schedule assumptions). Never put the full scope or pricing only in notes.","internalNotes = crew/office only — never shown on PDF, email, or client approval page.","If the client gave a PO / contract / plan / project ID, put those in the dedicated header fields — not buried in the line description.","Always pass propertyId when the work is at a known site so the quote links to the right address."],"good_example_single_line":{"clientId":"<client-uuid from forge_list_clients / forge_search>","propertyId":"<property-uuid from forge_list_properties>","title":"Vibe Lofts - Unit 401 Drywall Repairs","notes":"Quote for drywall repairs in Unit 401. Valid for 30 days. Price includes labour, materials, and cleanup. Paint colour to match existing as closely as possible.","lineItems":[{"description":"Unit 401 Drywall Repairs","detail":"• Replace damaged drywall\n• Tape and mud ready for primer\n• Prime all new drywall and mudded areas\n• Repaint walls to match existing\n• Reinstall baseboards and repaint baseboards\n• Includes labour, materials, and site cleanup","qty":1,"unit":"each","unitPrice":1219.73,"taxable":true}]},"good_example_multi_line_when_justified":{"why":"Client needs to see labour vs materials, or two distinct billable scopes with different units.","title":"HERD - Man Door Replacement - Voodoo Shed","lineItems":[{"description":"Man Door Supply & Install","detail":"Supply and install commercial man door complete with frame, hardware, weatherstripping, and finish to match existing as closely as possible. Includes removal and disposal of existing door.","qty":1,"unit":"each","unitPrice":1850,"taxable":true},{"description":"Concrete Threshold Patch","detail":"Chip and patch concrete threshold at door opening; broom finish to match adjacent slab.","qty":1,"unit":"each","unitPrice":275,"taxable":true}]},"bad_example":{"why":"Five nearly identical step lines with empty description fields — client sees blank 'Scope of work, specs…' placeholders and a messy quote. Also wrong fields (name instead of description).","lineItems":[{"name":"Replace drywall","qty":1,"unitPriceCents":24395},{"name":"Tape and mud ready for primer","qty":1,"unitPriceCents":24395},{"name":"Prime all new drywall and mudded areas","qty":1,"unitPriceCents":24395},{"name":"Repaint walls","qty":1,"unitPriceCents":24395},{"name":"Reinstall baseboards and repaint baseboards","qty":1,"unitPriceCents":24393}],"fix":"Collapse into ONE line: description 'Unit 401 Drywall Repairs', detail = all five steps as scope text, unitPrice = 1219.73."},"bad_example_missing_client":{"why":"Agent searches only for property nickname as client name, finds nothing, stops and asks the user to create the client.","wrong":"Search clients for 'Vibe Lofts' → 0 results → ask user whether to create Killam or stay in Jobber","fix":"Search 'Killam' as client; search 'Vibe' as property; create client only if truly missing; then create quote. Never hand the task back when tools can finish it."},"field_map":{"title":"Quote-level human title (property + work). Shown at top of quote.","notes":"Short client cover note only — validity, exclusions, assumptions. Not pricing. Not full scope.","internalNotes":"Staff/crew only. Never client-facing.","description":"Line item short title (REQUIRED). Client-facing main line on PDF and in app. e.g. 'Unit 401 Drywall Repairs'.","detail":"Line item full scope / specs (strongly recommended). Shown under the title. What will be done, materials, finish.","serviceNameSnapshot":"Optional alias for description on jobs/invoices — prefer description for quotes.","name":"Deprecated. Still accepted and mapped to description automatically — do not rely on it.","qty":"Quantity. Use 1 for lump-sum jobs.","unit":"each | lump sum | sqft | hrs | lf | etc.","unitPrice":"Dollars per unit (e.g. 1219.73). API converts to cents.","unitPriceCents":"Cents per unit (e.g. 121973). Use this OR unitPrice.","taxable":"Default true for GST/HST jurisdictions unless told otherwise.","clientId":"REQUIRED UUID from forge_list_clients / forge_search / forge_create_client.","propertyId":"Strongly recommended UUID from forge_list_properties / forge_create_property."},"checklist_before_create":["Did I search for the client with partial tokens (not only the site nickname)?","If client missing, did I create it with forge_create_client instead of stopping?","Did I resolve propertyId for the site (search by label/address under that client)?","Is there a clear human title?","Is there ONE line (or justified multi-lines) — not a step laundry list?","Does every line have description filled (not blank / not only 'name')?","Does detail explain the scope so the client knows what's included?","Is the price the real total (or real unit rate)?","Is notes a short cover note, not a dump of the scope?"]},"jobs":{"rules":["Same line-item quality standard as quotes.","Prefer carrying line items forward from the approved quote instead of inventing new wording.","Job-level description = short summary. scopeOfWork = narrative if needed. Pricing always lives in lineItems.","For jobs/invoices, serviceNameSnapshot may be used as a short item name (2–6 words) with description as the long detail — but for quotes always prefer description + detail.","Resolve client + property the same way as quotes before creating a job from scratch.","Valid job status values ONLY: scheduled | in_progress | substantially_complete | completed | invoiced | paid. There is NO cancelled/archived/closed status enum value.","To remove a duplicate or unwanted job from active lists: DELETE /api/v1/jobs/{id} (or job number like J-0021). This soft-archives via archivedAt — preferred.","Also accepted as archive: PATCH /api/v1/jobs/{id} with status cancelled|archived|closed (or archived:true). These map to soft-archive, not a status change.","Never rename a job to 'DUPLICATE - DO NOT USE' as a workaround — archive it properly so it leaves the active list.","Hard delete is not supported (billing/audit history). Archived jobs stay in the DB but are hidden from list/search/overview."]},"invoices":{"rules":["Same line-item shape as quotes: description (or serviceNameSnapshot) + detail/scope + price.","Prefer pulling line items from an approved quote or completed job rather than inventing new ones.","Client should recognize the same work they approved on the quote.","Do not re-split a clean lump-sum quote into step lines on the invoice.","SENT IS NOT IMMUTABLE for unpaid invoices. Fix INV-#### in place with forge_revise_invoice / forge_update_invoice (auto-reopens). NEVER create a new invoice number to correct addresses, lines, or totals. Price fix: forge_update_invoice { id, targetTotal: <dollars tax-inclusive> }.","PDF billing/site addresses are snapshots — refreshBillingAddress + refreshPropertyAddress (or overrides) on revise so the next PDF is correct.","If you already created a duplicate, void/trash it and revise the original."]},"endpoints":{"agent_bootstrap":"GET /api/v1/agent/bootstrap — ON CONNECT inject standingInstructions","agent_guide":"GET /api/v1/agent-guide","search":"GET /api/v1/search (or MCP forge_search) — clients, properties, jobs, quotes","list_clients":"MCP forge_list_clients / GET /api/v1/clients","create_client":"MCP forge_create_client / POST /api/v1/clients","list_properties":"MCP forge_list_properties","create_property":"MCP forge_create_property (or REST property create under client)","create_quote":"POST /api/v1/quotes","update_quote":"PATCH /api/v1/quotes/{id|Q-0013} or MCP forge_update_quote — line items + totals auto-recalc. Prefer update over recreate when fixing pricing.","send_quote":"POST /api/v1/quotes/{id}/send","create_job":"POST /api/v1/jobs","update_job":"PATCH /api/v1/jobs/{id} — status must be a valid workflow value (see jobs.rules)","archive_job":"DELETE /api/v1/jobs/{id} — soft-archive duplicate/unwanted jobs (preferred). Also PATCH status cancelled|archived|closed","openapi":"GET /api/v1/openapi.yaml","mcp_resource":"forge://agent-instructions (on connect), forge://agent-guide","update_invoice":"PATCH /api/v1/invoices/{id|INV-0038} or MCP forge_update_invoice — unpaid issued/emailed auto-reopen; same number. Prefer forge_revise_invoice for sent PDF fixes.","reopen_invoice":"POST /api/v1/invoices/{id}/reopen or forge_reopen_invoice","revise_invoice":"POST /api/v1/invoices/{id}/revise or forge_revise_invoice","void_invoice":"POST /api/v1/invoices/{id}/void or forge_void_invoice","mcp_tools":"forge_search, forge_list_clients, forge_create_client, forge_list_properties, forge_create_property, forge_create_quote, forge_update_quote, forge_send_quote, forge_create_invoice, forge_update_invoice, forge_revise_invoice, forge_reopen_invoice, forge_void_invoice, forge_create_job, forge_delete_*, forge_restore, forge_list_trash"},"agent_instructions":{"before_creating_quotes":"On connect read forge://agent-instructions or GET /api/v1/agent/bootstrap. Before quoting also read forge://agent-guide (or GET https://forge.kin.team/api/v1/agent-guide). Resolve or create client + property first. Then create a Jobber-style quote: one professional lump-sum line with description + full scope detail + total price. Do not stop because a client search returned empty — create the client and continue."},"quoteUpdates":{"title":"Updating quotes — fix pricing without recreating","rules":["When line items or totals are wrong on an existing quote, PATCH it — do not create a duplicate quote.","PATCH /api/v1/quotes/{id} or /api/v1/quotes/Q-0013 with { lineItems: [...] }. Totals recalculate automatically.","MCP: forge_update_quote with quoteId + lineItems. Each line MUST include description (short title) AND detail (1-2 sentences or real bullets) as separate fields.","If you only send description, Forge treats that as a title rewrite and DROPS the old detail — leftover phones/rates/draft-only cannot survive.","Never dump address, phones, hourly rates, or internal status into description or detail. Those belong on the client/property record or internalNotes.","Never claim a quote was updated unless the API returned 200 with the new totals.","Root cause of past 500s: writing api key id into updated_by — fixed; agents should retry failed patches."]},"trashAndCleanup":{"title":"Trash / soft-delete — clean up junk test records","rules":["Forge never hard-deletes clients, properties, quotes, jobs, or invoices. DELETE moves them to trash (archivedAt).","If you create test/junk records while probing the API, DELETE them immediately — do not leave them for the user.","DELETE /api/v1/clients/{id}, /properties/{id}, /quotes/{id|Q-0013}, /jobs/{id|J-0021}, /invoices/{id}","Restore: POST /api/v1/{type}/{id}/restore","List trash: GET /api/v1/trash?type=all|client|property|quote|job|invoice","MCP: forge_delete_client|property|quote|job|invoice, forge_restore, forge_list_trash","For issued invoices the user wants cancelled, prefer void — trash hides from lists but is mainly for drafts/mistakes/tests.","Users can review and restore everything under Trash in the Forge app sidebar."]}}