{"openapi":"3.1.0","info":{"title":"modellus","version":"1.0.0","description":"Chases documents, keeps borrowers posted in English or Spanish, and re-checks every condition while you originate.\n\nThe same actions are available through four doors: this API, the in-app\nchat, an MCP server at `/api/mcp` to connect Claude or ChatGPT,\nand the screens.\n\nAuthentication: send your key in the `Authorization: Bearer mll_...` header.\nKeys are created from the app."},"servers":[{"url":"https://modellus-app.vercel.app"}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"modellus API key (starts with `mll_`)."}}},"tags":[{"name":"cuenta","description":"Account"},{"name":"contacts","description":"Contacts"},{"name":"pipeline","description":"Pipeline"},{"name":"arive","description":"Arive"},{"name":"documents","description":"Documents"},{"name":"communication","description":"Communication"},{"name":"tasks","description":"Tasks"},{"name":"insights","description":"Insights"},{"name":"team","description":"Team"}],"paths":{"/api/v1/resumen_cuenta":{"post":{"operationId":"resumen_cuenta","summary":"Returns the asking account's own data: display name and when it was created","description":"Returns the asking account's own data: display name and when it was created. Use it at the start of a conversation to know who you are talking to, or when asked \"who am I?\".","tags":["cuenta"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"nombre":{"type":"string"},"correo":{"type":["string","null"]},"creada":{"type":["string","null"]}},"required":["nombre","correo","creada"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/export_data":{"post":{"operationId":"export_data","summary":"Exports this account's data as JSON: contacts, loans and pre-quals, the people on each file, financial items, the document checklist, tasks and messages","description":"Exports this account's data as JSON: contacts, loans and pre-quals, the people on each file, financial items, the document checklist, tasks and messages. Use it when the user wants a backup, wants to take their data somewhere else, or asks for \"everything on the Martinez file\" in one piece. Ask for one table with `que` when they only need part of it. Uploaded files themselves are not included — their paths and status are.","tags":["account"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"que":{"type":"array","items":{"type":"string","enum":["contacts","loans","participants","items","documents","tasks","messages"]},"description":"Which tables to include. Omit for all of them."},"limite":{"type":"integer","minimum":1,"maximum":5000,"description":"Max rows per table. Default 1000."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"exportado_el":{"type":"string"},"conteos":{"type":"object","additionalProperties":{"type":"number"}},"datos":{"type":"object","additionalProperties":{"type":"array","items":{"type":"object","additionalProperties":{}}}}},"required":["exportado_el","conteos","datos"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/search":{"post":{"operationId":"search","summary":"Searches the whole account at once — contacts, loans, pre-quals and tasks — and returns the top matches per group","description":"Searches the whole account at once — contacts, loans, pre-quals and tasks — and returns the top matches per group. Use it when the user searches for something and it is not clear what kind of thing it is (\"search Robert Miller\", \"anything on 412 Elm?\", \"find insurance\"). If you already know it is a loan or pre-qual, use find_loan; if you know it is a person, use find_contact — those return the full record. Accepts a name, an email, a phone fragment, a property address, a loan number or a task title.","tags":["cuenta"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string","minLength":2,"description":"What to look for, as the user says it"}},"required":["query"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"resumen":{"type":"string"},"contactos":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"nombre":{"type":"string"},"roles":{"type":"array","items":{"type":"string"}},"email":{"type":["string","null"]},"telefono":{"type":["string","null"]}},"required":["id","nombre","roles","email","telefono"],"additionalProperties":false}},"loans":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"nombre":{"type":"string"},"loan_number":{"type":["string","null"]},"estado":{"type":"string"},"estado_label":{"type":"string"}},"required":["id","nombre","loan_number","estado","estado_label"],"additionalProperties":false}},"prequals":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"nombre":{"type":"string"},"loan_number":{"type":["string","null"]},"estado":{"type":"string"},"estado_label":{"type":"string"}},"required":["id","nombre","loan_number","estado","estado_label"],"additionalProperties":false}},"tareas":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"titulo":{"type":"string"},"estado":{"type":"string"},"vence_el":{"type":["string","null"]}},"required":["id","titulo","estado","vence_el"],"additionalProperties":false}}},"required":["resumen","contactos","loans","prequals","tareas"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/adjust_view":{"post":{"operationId":"adjust_view","summary":"Adjusts the list screens: the pre-qual pipeline, the loan pipeline, or the contacts list","description":"Adjusts the list screens: the pre-qual pipeline, the loan pipeline, or the contacts list. Shows or hides columns (\"show the loan number in the pipeline\"), REORDERS a column (\"put the amount column first\", \"move closing date before created\"), expands or collapses COLUMN GROUPS (file, loan, rate, contacts, property, title, dates, docs — \"expand the property columns\"; Loan, Rate, Property and Title exist only on the loan pipeline, since a pre-qual has no property or rate yet), turns a WHOLE GROUP of columns on or off at once (\"show me all the title columns\", \"hide every date\"), REORDERS THE GROUPS themselves (\"put the documents block first\", \"move dates before property\"), PINS a column to the left or right edge so it stays in view while scrolling sideways (\"keep the loan amount visible\", \"pin the closing date to the right\", \"unpin the rate\"), switches the LIST LAYOUT between the table of rows and the grid of cards (\"show the loans as cards\", \"back to the table view\") — each list remembers its own, and on a phone it is always cards — changes row density (compact/standard/comfortable), SORTS the list by a column (\"sort the loans by amount, biggest first\") and FILTERS it by the values of a column (\"only show FHA loans\", \"just the ones assigned to Ana\", \"clear the filters\"). The sort and the filters are what the column header menus do, and they stick for next time. Everything here is PER PERSON: it changes how the caller sees the list, never how their teammates see it. To read the pipeline itself use view_pipeline; to change the stage of one file use move_stage.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"vista":{"type":"string","enum":["prequal","loan","contacts"],"description":"Which list to adjust: the pre-qual pipeline, the loan pipeline, or contacts."},"mostrar":{"type":"array","items":{"type":"string"},"description":"Column ids to turn on. Empty array if none."},"ocultar":{"type":"array","items":{"type":"string"},"description":"Column ids to turn off. Empty array if none."},"mover":{"anyOf":[{"type":"object","properties":{"columna":{"type":"string","description":"The column id to move."},"antes":{"type":["string","null"],"description":"Column id to place it right BEFORE. Null if unused."},"despues":{"type":["string","null"],"description":"Column id to place it right AFTER. Null if unused."},"posicion":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Absolute position among visible columns, 1-based (1 = first). Null if unused."}},"required":["columna","antes","despues","posicion"],"additionalProperties":false},{"type":"null"}],"description":"Use when the user asks to reorder a column (\"put amount first\", \"move closing date before created\"). Give exactly ONE of antes, despues or posicion. In the pipelines a column always stays inside its group, so it lands at the closest spot within it. Null if the user did not ask to reorder."},"densidad":{"anyOf":[{"type":"string","enum":["compact","standard","comfortable"]},{"type":"null"}],"description":"Row density, if the user asked to change it. Null otherwise."},"forma":{"anyOf":[{"type":"string","enum":["tabla","tarjetas"]},{"type":"null"}],"description":"How to lay the list out on a desktop screen: 'tabla' is the row table with all the columns, 'tarjetas' is the grid of cards. This is the pair of icons above the list, and it sticks per list. Null if the user did not ask to change it."},"expandir_grupos":{"type":"array","items":{"type":"string","enum":["file","loan","rate","contacts","property","title","dates","docs"]},"description":"Column groups to expand into their child columns. Empty array if none."},"colapsar_grupos":{"type":"array","items":{"type":"string","enum":["file","loan","rate","contacts","property","title","dates","docs"]},"description":"Column groups to fold back to ONE read-only summary column — only when the user wants them out of the way; folded columns cannot be edited. Empty array if none."},"mostrar_grupos":{"type":"array","items":{"type":"string","enum":["file","loan","rate","contacts","property","title","dates","docs"]},"description":"Groups whose EVERY column should be turned on at once (\"show all the title columns\", \"I want the whole dates block\"). This is about WHICH columns exist in the table, not about folding: use expandir_grupos when the user only wants a folded group opened back up. The group is expanded too, since turning columns on means wanting to see them. Empty array if none."},"ocultar_grupos":{"type":"array","items":{"type":"string","enum":["file","loan","rate","contacts","property","title","dates","docs"]},"description":"Groups whose every column should be turned off at once (\"get rid of all the document columns\"). Prefer colapsar_grupos when the user just wants them out of the way for now — folding keeps them one click away, this one takes them out of the table. Empty array if none."},"mover_grupo":{"anyOf":[{"type":"object","properties":{"grupo":{"type":"string","enum":["file","loan","rate","contacts","property","title","dates","docs"],"description":"The group to move."},"antes":{"anyOf":[{"type":"string","enum":["file","loan","rate","contacts","property","title","dates","docs"]},{"type":"null"}],"description":"Group id to place it right BEFORE. Null if unused."},"despues":{"anyOf":[{"type":"string","enum":["file","loan","rate","contacts","property","title","dates","docs"]},{"type":"null"}],"description":"Group id to place it right AFTER. Null if unused."},"posicion":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Absolute position among the groups, 1-based (1 = leftmost). Null if unused."}},"required":["grupo","antes","despues","posicion"],"additionalProperties":false},{"type":"null"}],"description":"Use when the user asks to move a whole BLOCK of columns (\"put the documents group first\", \"move dates before property\"). Give exactly ONE of antes, despues or posicion. To move a single column use mover instead. Null if the user did not ask."},"orden":{"anyOf":[{"type":"object","properties":{"columna":{"type":"string","description":"The column id to sort by."},"direccion":{"type":"string","enum":["asc","desc"],"description":"'asc' = A→Z / smallest first, 'desc' = the other way."}},"required":["columna","direccion"],"additionalProperties":false},{"type":"null"}],"description":"Sort the list by a column. Pass null to LEAVE THE SORT AS IT IS. To remove it, use quitar_orden."},"quitar_orden":{"type":"boolean","description":"true = go back to no sorting. false otherwise."},"filtros":{"type":"array","items":{"type":"object","properties":{"columna":{"type":"string","description":"The column id to filter by."},"valores":{"type":"array","items":{"type":"string"},"description":"The values to keep, exactly as they are stored (fha, purchase, a date, a name). An EMPTY array clears this column's filter."}},"required":["columna","valores"],"additionalProperties":false},"description":"Filters by column value, like the header menu. Each entry REPLACES that column's filter. Empty array if the user did not ask to filter."},"quitar_filtros":{"type":"boolean","description":"true = clear every column filter. false otherwise."},"fijar":{"type":"array","items":{"type":"object","properties":{"columna":{"type":"string","description":"The column id to pin or unpin."},"lado":{"type":"string","enum":["izquierda","derecha","ninguno"],"description":"'izquierda' pins it to the left edge, 'derecha' to the right, 'ninguno' unpins it."}},"required":["columna","lado"],"additionalProperties":false},"description":"Columns to pin to an edge so they stay visible while scrolling sideways, or to unpin. Empty array if the user did not ask. A hidden column cannot be pinned: show it in the same call."}},"required":["vista","mostrar","ocultar","mover","densidad","forma","expandir_grupos","colapsar_grupos","mostrar_grupos","ocultar_grupos","mover_grupo","orden","quitar_orden","filtros","quitar_filtros","fijar"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"vista":{"type":"string"},"columnas":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"visible":{"type":"boolean"}},"required":["id","label","visible"],"additionalProperties":false}},"grupos":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"expandido":{"type":"boolean"}},"required":["id","expandido"],"additionalProperties":false}},"densidad":{"type":"string"},"forma":{"type":"string"},"fijadas":{"type":"object","properties":{"izquierda":{"type":"array","items":{"type":"string"}},"derecha":{"type":"array","items":{"type":"string"}}},"required":["izquierda","derecha"],"additionalProperties":false},"orden":{"anyOf":[{"type":"object","properties":{"columna":{"type":"string"},"direccion":{"type":"string"}},"required":["columna","direccion"],"additionalProperties":false},{"type":"null"}]},"filtros":{"type":"array","items":{"type":"object","properties":{"columna":{"type":"string"},"valores":{"type":"array","items":{"type":"string"}}},"required":["columna","valores"],"additionalProperties":false}}},"required":["vista","columnas","grupos","densidad","forma","fijadas","orden","filtros"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/cambiar_nombre":{"post":{"operationId":"cambiar_nombre","summary":"Changes the account's display name","description":"Changes the account's display name. Use it only when explicitly asked to change or correct how the user is called.","tags":["cuenta"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"nombre":{"type":"string","minLength":2,"maxLength":80,"description":"The new name, exactly as they want it displayed"}},"required":["nombre"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"nombre":{"type":"string"}},"required":["nombre"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_profile":{"post":{"operationId":"view_profile","summary":"Returns the profile of the person asking: their display name, email, phone, photo, NMLS number, whether their email is verified, whether an email change is still waiting to be confirmed, and their role in this account (owner, loan officer, or assistant)","description":"Returns the profile of the person asking: their display name, email, phone, photo, NMLS number, whether their email is verified, whether an email change is still waiting to be confirmed, and their role in this account (owner, loan officer, or assistant). Use it when the user asks \"what is my name here\", \"what email am I signed in with\", \"is my email verified\", \"did my email change go through\", \"what can I do in this account\", or before changing any of it with update_profile or change_email.","tags":["cuenta"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"photo_url":{"type":["string","null"]},"nmls":{"type":["string","null"]},"email_verified":{"type":["boolean","null"]},"pending_email":{"type":["string","null"]},"pending_email_since":{"type":["string","null"]},"role":{"type":"string"},"owns_this_account":{"type":"boolean"}},"required":["name","email","phone","photo_url","nmls","email_verified","pending_email","pending_email_since","role","owns_this_account"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/update_profile":{"post":{"operationId":"update_profile","summary":"Changes the asking person's own profile: display name, phone number, NMLS number, or profile photo","description":"Changes the asking person's own profile: display name, phone number, NMLS number, or profile photo. Use it when the user says \"change my name to...\", \"my phone is...\", \"update my NMLS\", \"remove my profile picture\". Only the fields given are touched; the rest stay as they were. To see what they are now, use view_profile.","tags":["cuenta"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":80,"description":"The new display name, exactly as they want it shown."},"phone":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}],"description":"Their phone number, as they say it. null clears it."},"nmls":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}],"description":"Their NMLS license number — it goes on the pre-approval letters. Only the account owner has one. null clears it."},"photo_url":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"description":"The URL of their profile photo. Pass null to remove it. Uploading a new picture is done from the settings screen — from here only removing it makes sense."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":["string","null"]},"nmls":{"type":["string","null"]},"photo_url":{"type":["string","null"]},"changed":{"type":"array","items":{"type":"string"}}},"required":["name","phone","nmls","photo_url","changed"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/reset_password":{"post":{"operationId":"reset_password","summary":"Emails the signed-in person a link to set a new password","description":"Emails the signed-in person a link to set a new password. Use it when the user says \"I want to change my password\", \"reset my password\", \"I forgot my password\". Never ask them to type the new password here — the link is where they set it.","tags":["cuenta"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"sent_to":{"type":"string"},"note":{"type":"string"}},"required":["sent_to","note"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/resend_email_verification":{"post":{"operationId":"resend_email_verification","summary":"Resends the email that confirms the signed-in person's address","description":"Resends the email that confirms the signed-in person's address. Use it when the user says \"I never got the confirmation email\", \"resend the verification\", or after view_profile shows their email is not verified yet.","tags":["cuenta"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"sent_to":{"type":"string"},"already_verified":{"type":"boolean"},"note":{"type":"string"}},"required":["sent_to","already_verified","note"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/change_email":{"post":{"operationId":"change_email","summary":"Starts changing the email address the signed-in person uses to sign in","description":"Starts changing the email address the signed-in person uses to sign in. Use it when the user says \"change my email to...\", \"I lost access to my email\", \"I want to sign in with another address\", or \"update my login email\". It does not change anything on its own: it sends a confirmation link to the new address and to the current one, and the change only takes effect once both are opened. Calling it again with the same address sends the links again. To see whether a change is still waiting, use view_profile.","tags":["cuenta"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","minLength":3,"maxLength":255,"format":"email","description":"The new address they want to sign in with. Ask for it if they did not say it — never guess it from a contact or from a message."}},"required":["email"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"correo_actual":{"type":"string"},"correo_pendiente":{"type":"string"},"reenviado":{"type":"boolean"},"note":{"type":"string"}},"required":["correo_actual","correo_pendiente","reenviado","note"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_integrations":{"post":{"operationId":"view_integrations","summary":"Shows what this account has connected — Arive (the loan origination system) and the user's own email account (Gmail/Outlook) — and whether each one is actually working: its status, when it last worked, and what the last failure said","description":"Shows what this account has connected — Arive (the loan origination system) and the user's own email account (Gmail/Outlook) — and whether each one is actually working: its status, when it last worked, and what the last failure said. Use it when the user asks what is connected, where their messages are sent from, why their emails are not going out from their own address, or right after they finish an email authorization to confirm it worked. To test a connection right now use check_integration; to change any of it, use connect_arive, connect_email or disconnect_email. It also shows the Arive cutoff date — from when loans get imported — which set_arive_cutoff changes, and whether Arive live updates are on.","tags":["cuenta"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"arive":{"type":"object","properties":{"connected":{"type":"boolean"},"status":{"type":"string","description":"active, inactive, error, expired or forbidden."},"status_label":{"type":"string"},"last_ok":{"type":["string","null"],"description":"When it last answered correctly (ISO)."},"message":{"type":["string","null"],"description":"What the last failure said."},"what_to_do":{"type":["string","null"]},"import_cutoff":{"type":["string","null"],"description":"The cutoff date in effect (YYYY-MM-DD or ISO): loans Arive created before it are never imported. Null means no cutoff — everything in Arive is importable. Change it with set_arive_cutoff."},"live":{"type":"boolean","description":"Whether Arive pushes changes here within seconds. When false, changes only arrive with the sync that runs every 15 minutes. Turn it on with set_arive_live."},"live_since":{"type":["string","null"]},"last_live_event_at":{"type":["string","null"],"description":"When the last live update arrived (ISO). Null = none yet."}},"required":["connected","status","status_label","last_ok","message","what_to_do","import_cutoff","live","live_since","last_live_event_at"],"additionalProperties":false},"email":{"type":"object","properties":{"available":{"type":"boolean","description":"Whether email connections are enabled on this server."},"connected":{"type":"boolean"},"provider":{"type":["string","null"]},"address":{"type":["string","null"]},"pending_authorization":{"type":"boolean","description":"An authorization link was created and not completed yet."},"status":{"type":"string"},"status_label":{"type":"string"},"last_ok":{"type":["string","null"]},"message":{"type":["string","null"]},"what_to_do":{"type":["string","null"]}},"required":["available","connected","provider","address","pending_authorization","status","status_label","last_ok","message","what_to_do"],"additionalProperties":false},"can_import_contacts":{"type":"boolean","description":"Whether the address book can be imported — true whenever an email account is connected, both providers include their contacts."},"sending_from":{"type":"string","description":"The address outgoing emails leave from right now."}},"required":["arive","email","can_import_contacts","sending_from"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/check_integration":{"post":{"operationId":"check_integration","summary":"Tests a connection right now and reports whether it works: Arive (it authenticates and reads a loan) or the connected email account","description":"Tests a connection right now and reports whether it works: Arive (it authenticates and reads a loan) or the connected email account. Use it when the user suspects something is down (\"is Arive working?\", \"my emails are not going out from my address\", \"check the connection\"), or after fixing credentials. If it comes back expired or denied, the fix is connecting it again with connect_arive or connect_email — retrying will not help.","tags":["cuenta"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"integration":{"type":"string","enum":["arive","email"],"description":"Which connection to test."}},"required":["integration"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"integration":{"type":"string"},"status":{"type":"string"},"status_label":{"type":"string"},"message":{"type":["string","null"]},"what_to_do":{"type":["string","null"]},"resumen":{"type":"string"}},"required":["integration","status","status_label","message","what_to_do","resumen"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/connect_email":{"post":{"operationId":"connect_email","summary":"Starts connecting the user's own email account (Gmail or Outlook) so their messages go out from their real address instead of the generic Modellus one, and so their address book can be imported","description":"Starts connecting the user's own email account (Gmail or Outlook) so their messages go out from their real address instead of the generic Modellus one, and so their address book can be imported. Use it when the user asks to connect their email, or when they want to import their contacts and no email account is connected yet. It returns an authorization link — show it to the user and tell them to open it, sign in, and come back; then check view_integrations to confirm it worked.","tags":["cuenta"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"provider":{"type":"string","enum":["gmail","outlook"],"description":"Which email provider the user wants to connect."}},"required":["provider"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"authorization_url":{"type":"string","description":"Send the user here to authorize. Expires if unused."},"provider":{"type":"string"}},"required":["authorization_url","provider"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/disconnect_email":{"post":{"operationId":"disconnect_email","summary":"Disconnects the user's own email account, so messages go back out through the generic Modellus sender and their address book is no longer reachable","description":"Disconnects the user's own email account, so messages go back out through the generic Modellus sender and their address book is no longer reachable. Use it when the user asks to disconnect, unlink or remove their Gmail/Outlook connection. Messages already in the queue are not affected — they will simply leave from the generic sender.","tags":["cuenta"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"was_connected":{"type":["string","null"],"description":"The address that was disconnected."}},"required":["ok","was_connected"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_google_contacts":{"post":{"operationId":"view_google_contacts","summary":"Shows the Google Contacts connection: which Google account is linked (name and email), whether it is working, how many contacts already exist on both sides, how many are still only in Modellus, how many are waiting because Google could not be reached, and when it last ran","description":"Shows the Google Contacts connection: which Google account is linked (name and email), whether it is working, how many contacts already exist on both sides, how many are still only in Modellus, how many are waiting because Google could not be reached, and when it last ran. Use it when the user asks whether Google is connected, why their contacts are not syncing, how the sync is going, or right after they finish the Google authorization. To change any of it: connect_google to connect or reconnect, sync_google_contacts to run a sync now, preview_google_push / push_contacts_to_google to put Modellus contacts into Google, disconnect_google to stop syncing.","tags":["cuenta"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"available":{"type":"boolean","description":"Whether Google connections are enabled on this server."},"connected":{"type":"boolean"},"account_name":{"type":["string","null"],"description":"The name on the connected Google account."},"account_email":{"type":["string","null"]},"pending_authorization":{"type":"boolean","description":"An authorization link was created and not completed yet."},"status":{"type":"string","description":"active, inactive, error, expired or forbidden."},"status_label":{"type":"string"},"what_to_do":{"type":["string","null"]},"last_ok":{"type":["string","null"]},"message":{"type":["string","null"]},"in_both":{"type":"number","description":"Contacts that exist in Modellus and in Google."},"only_in_modellus":{"type":"number","description":"Contacts not in Google yet. push_contacts_to_google creates them there — but only after the user agrees, seeing this number."},"waiting_on_google":{"type":"number","description":"Contacts whose change could not reach Google. They retry by themselves; nothing was lost."},"first_import_done":{"type":"boolean"},"pushing_to_google":{"type":"boolean","description":"Whether the user allowed Modellus to write into Google."},"work_left":{"type":"boolean","description":"A sync is half done; the next run finishes it."},"last_run":{"type":["string","null"]}},"required":["available","connected","account_name","account_email","pending_authorization","status","status_label","what_to_do","last_ok","message","in_both","only_in_modellus","waiting_on_google","first_import_done","pushing_to_google","work_left","last_run"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/connect_google":{"post":{"operationId":"connect_google","summary":"Starts connecting the user's Google account so their contacts stay in sync between Modellus and contacts","description":"Starts connecting the user's Google account so their contacts stay in sync between Modellus and contacts.google.com, in both directions. Use it when the user asks to connect Google, sync their Google contacts, bring their Google address book in, or when they ask for contact sync and no Google account is connected yet. It returns an authorization link — show it to the user and tell them to open it, sign in, and accept the permission to manage their contacts; then call view_google_contacts to confirm it worked and to start the first import. This is NOT connect_email: that one is for sending mail from their own address and only reads the address book.","tags":["cuenta"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"authorization_url":{"type":"string","description":"Send the user here to authorize. Expires if unused."},"what_it_asks_for":{"type":"string","description":"The permission the user will be asked to accept."}},"required":["authorization_url","what_it_asks_for"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/disconnect_google":{"post":{"operationId":"disconnect_google","summary":"Disconnects the Google account used for contact sync, so contacts stop flowing between Modellus and contacts","description":"Disconnects the Google account used for contact sync, so contacts stop flowing between Modellus and contacts.google.com. Use it when the user asks to disconnect, unlink or stop syncing Google contacts. Nothing is deleted on either side — the contacts already imported stay, and the ones already in Google stay too; they simply stop updating each other. This does not touch their Gmail sending connection (that one is disconnect_email).","tags":["cuenta"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"was_connected":{"type":["string","null"],"description":"The Google account that was disconnected."},"nothing_deleted":{"type":"boolean","description":"Always true. Say so: people expect disconnecting to erase."}},"required":["ok","was_connected","nothing_deleted"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/sync_google_contacts":{"post":{"operationId":"sync_google_contacts","summary":"Runs a Google contact sync right now: brings in contacts from contacts","description":"Runs a Google contact sync right now: brings in contacts from contacts.google.com that Modellus does not have, matches the ones it already has instead of duplicating them, and — only if the user already allowed it with push_contacts_to_google — sends changes the other way too. Use it when the user asks to sync, refresh or update their Google contacts, when they just connected Google, or when they say a contact they changed has not shown up on the other side. It NEVER deletes anything on either side. Large address books are done in batches: if it says more is left, just call it again.","tags":["contacts"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"read_from_google":{"type":"number"},"brought_in":{"type":"number","description":"New contacts created in Modellus."},"matched":{"type":"number","description":"Google contacts matched to people already in the rolodex."},"already_in_sync":{"type":"number"},"added_to_google":{"type":"number"},"updated_in_google":{"type":"number"},"waiting_on_google":{"type":"number","description":"Changes that could not reach Google. They retry alone."},"deleted_in_google_ignored":{"type":"number","description":"Contacts deleted in Google that Modellus kept, on purpose. Deleting is a decision, never a side effect of a sync."},"more_left":{"type":"boolean","description":"True when the address book was larger than one batch."},"summary":{"type":"string","description":"One line to read back to the user."}},"required":["read_from_google","brought_in","matched","already_in_sync","added_to_google","updated_in_google","waiting_on_google","deleted_in_google_ignored","more_left","summary"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/preview_google_push":{"post":{"operationId":"preview_google_push","summary":"Says how many Modellus contacts would be CREATED in the user's Google address book, without creating anything","description":"Says how many Modellus contacts would be CREATED in the user's Google address book, without creating anything. Use it BEFORE push_contacts_to_google, and whenever the user asks how many of their contacts are missing from Google or what would happen if they turned the sync both ways. Always show the number to the user and let them decide — this writes into their personal address book, so it is their call, not a side effect of connecting.","tags":["contacts"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"total_contacts":{"type":"number"},"already_in_google":{"type":"number"},"would_be_created":{"type":"number","description":"How many new entries would appear in their Google contacts."},"nothing_deleted":{"type":"boolean","description":"Always true. Pushing only creates and updates."}},"required":["total_contacts","already_in_google","would_be_created","nothing_deleted"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/push_contacts_to_google":{"post":{"operationId":"push_contacts_to_google","summary":"Creates the Modellus contacts that are missing from the user's Google address book and keeps changes flowing that way from then on, writing the Modellus contact id into a Google custom field so nothing is ever duplicated","description":"Creates the Modellus contacts that are missing from the user's Google address book and keeps changes flowing that way from then on, writing the Modellus contact id into a Google custom field so nothing is ever duplicated. Call preview_google_push FIRST and only call this once the user has SEEN how many contacts would be created and said yes — it writes into their personal address book. It never deletes anything in Google. Large books go in batches: if it says more is left, call it again, or let the scheduled sync finish it. Use stop=true when the user asks to stop writing to Google (what is already there stays).","tags":["contacts"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"stop":{"type":"boolean","description":"True to STOP pushing Modellus contacts into Google from now on. Nothing already in Google is removed. False to start or continue."}},"required":["stop"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"pushing":{"type":"boolean","description":"Whether Modellus writes into Google from now on."},"created_in_google":{"type":"number"},"updated_in_google":{"type":"number"},"already_matched":{"type":"number"},"waiting_on_google":{"type":"number","description":"Contacts whose change could not get through. They retry alone."},"more_left":{"type":"boolean"},"summary":{"type":"string"}},"required":["pushing","created_in_google","updated_in_google","already_matched","waiting_on_google","more_left","summary"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/find_contact":{"post":{"operationId":"find_contact","summary":"Search the rolodex by anything the user says: a name (even partial or misspelled), a phone number or its last digits, an email, a tag, or a role like \"realtor\" or \"title company\"","description":"Search the rolodex by anything the user says: a name (even partial or misspelled), a phone number or its last digits, an email, a tag, or a role like \"realtor\" or \"title company\". Use it whenever the user asks about a person (\"what is Maria's number?\", \"show me my appraisers\"), before saving someone you are not sure exists, and to get the contact details other actions need. Returns matching contacts with their roles, tags and key data; when few match, it includes their latest activity.","tags":["contacts"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string","description":"Free text: name, email, phone or fragment. Omit to browse by role or tag."},"role":{"type":"string","enum":["borrower","co_borrower","realtor","listing_agent","selling_agent","seller","title_company","escrow_officer","insurance_company","building_master_insurance","appraiser","appraisal_management_company","management_company_building","developer","processor","underwriter","closer","loan_officer_assistant","junior_loan_officer","lender","account_executive","account_manager","mortgage_broker","hard_money_lender","attorney","cpa","financial_advisor","home_inspector","contractor","hoa","surveyor","notary","referral_source","past_client","builder","property_manager","alt_contact","other"],"description":"Only contacts with this role, e.g. \"realtor\" or \"past_client\"."},"tag":{"type":"string","description":"Only contacts carrying this tag."},"limit":{"type":"integer","minimum":1,"maximum":25,"description":"Max results, default 10."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"number"},"contacts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"name":{"type":"string"},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"roles":{"type":"array","items":{"type":"string"},"description":"Role keys, exactly as save_contact takes them (\"listing_agent\"). To remove one, send save_contact this list without it."},"roles_label":{"type":"array","items":{"type":"string"},"description":"The same roles written out, to say them to the user (\"Listing Agent\")."},"tags":{"type":"array","items":{"type":"string"}},"sms_opt_out":{"type":"boolean"},"company":{"type":["string","null"]},"preferred_language":{"type":"string"},"assistant":{"anyOf":[{"type":"object","properties":{"name":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]}},"required":["name","email","phone"],"additionalProperties":false},{"type":"null"}],"description":"Who handles this person. When present, every email, text and WhatsApp sent to the contact is copied to them."},"recent_events":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"detail":{"type":"string"},"when":{"type":"string"}},"required":["action","detail","when"],"additionalProperties":false}}},"required":["id","name","email","phone","roles","roles_label","tags","sms_opt_out","company","preferred_language","assistant","recent_events"],"additionalProperties":false}}},"required":["total","contacts"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/save_contact":{"post":{"operationId":"save_contact","summary":"Create a new contact or update an existing one","description":"Create a new contact or update an existing one. Use it when the user introduces someone new (\"add Maria Torres, she's a realtor, 704-555-0142\") or changes someone's details (\"John has a new email\"). Updates when given an id or when the email/phone matches an existing contact exactly; if the new person looks like a probable duplicate it does NOT create — it returns the candidates so you can confirm with the user and either update/merge them (merge_contacts) or retry with create_anyway. When provided, roles and tags REPLACE the existing lists — to remove one (\"she is not the listing agent anymore\", \"drop the vip tag\") send the list without it, and send an empty list to clear them all. Any other field takes an empty string to clear it (\"he has no company\"). What you do not send stays as it was. When someone works through an assistant (\"her assistant is Dana, dana@titleco.com\"), save assistant_name, assistant_email and assistant_phone: from then on every email, text or WhatsApp sent to that contact is copied to their assistant.","tags":["contacts"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"The contact id, when updating someone find_contact already returned."},"name":{"type":"string","maxLength":160,"description":"Full name. Required when creating."},"email":{"type":"string"},"phone":{"type":"string","description":"Any format; stored as E.164, US +1 assumed for 10 digits."},"roles":{"type":"array","items":{"type":"string","enum":["borrower","co_borrower","realtor","listing_agent","selling_agent","seller","title_company","escrow_officer","insurance_company","building_master_insurance","appraiser","appraisal_management_company","management_company_building","developer","processor","underwriter","closer","loan_officer_assistant","junior_loan_officer","lender","account_executive","account_manager","mortgage_broker","hard_money_lender","attorney","cpa","financial_advisor","home_inspector","contractor","hoa","surveyor","notary","referral_source","past_client","builder","property_manager","alt_contact","other"]},"description":"Business roles, e.g. [\"realtor\", \"referral_source\"]. Replaces the list."},"tags":{"type":"array","items":{"type":"string","minLength":1,"maxLength":60},"description":"Replaces the list."},"address":{"type":"string","maxLength":240},"company":{"type":"string","maxLength":120},"nmls_license":{"type":"string","maxLength":40},"preferred_language":{"type":"string","enum":["en","es"]},"referral_source":{"type":"string","maxLength":160,"description":"Who or what referred them."},"notes":{"type":"string","maxLength":4000,"description":"Free-form background notes."},"work_phone":{"type":"string","maxLength":40,"description":"Office line. Same format as phone; stored as E.164."},"home_phone":{"type":"string","maxLength":40,"description":"Home line. Same format as phone; stored as E.164."},"phone_extension":{"type":"string","maxLength":12,"description":"Extension for the office line."},"occupation":{"type":"string","maxLength":120},"birth_date":{"type":"string","description":"ISO date (1985-04-12). Worth having: birthdays keep a rolodex warm."},"frequency":{"anyOf":[{"type":"string","enum":["high","mid","low"]},{"type":"string","const":""}],"description":"How often to touch base with them. Empty string clears it."},"last_contacted":{"type":"string","description":"ISO date of the last real conversation with them."},"assistant_name":{"type":"string","maxLength":160,"description":"Who handles this person's calendar and inbox."},"assistant_email":{"type":"string","maxLength":160,"description":"The assistant gets a copy of every email sent to the contact."},"assistant_phone":{"type":"string","maxLength":40,"description":"The assistant gets a copy of every text and WhatsApp sent to the contact. Same format as phone; stored as E.164."},"create_anyway":{"type":"boolean","description":"Set true ONLY after the user confirmed the probable duplicate is a different person."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["created","updated","duplicate_found"]},"contact":{"type":"object","properties":{"id":{"type":"number"},"name":{"type":"string"},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"company":{"type":["string","null"]},"roles":{"type":"array","items":{"type":"string"},"maxItems":50},"tags":{"type":"array","items":{"type":"string"},"maxItems":50}},"required":["id","name","email","phone","company","roles","tags"],"additionalProperties":false},"duplicates":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"name":{"type":"string"},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"company":{"type":["string","null"]},"roles":{"type":"array","items":{"type":"string"},"maxItems":50},"score":{"type":"number"},"reason":{"type":"string"}},"required":["id","name","email","phone","company","roles","score","reason"],"additionalProperties":false},"description":"Present when status is duplicate_found: confirm with the user before retrying."}},"required":["status"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/merge_contacts":{"post":{"operationId":"merge_contacts","summary":"Merge two contacts that are the same person into one","description":"Merge two contacts that are the same person into one. Use it after save_contact or the import reported a probable duplicate and the user confirmed they are indeed the same, or when the user says \"these two are the same person\". Takes the two ids (find_contact gives them). The FIRST contact wins on conflicting fields; roles, tags and data are combined, and a non-null email/phone is never lost. The second contact is deleted. Never call it without the user confirming which two to merge.","tags":["contacts"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"keep_id":{"type":"integer","description":"The contact that survives and wins on conflicts."},"merge_id":{"type":"integer","description":"The duplicate that gets absorbed and deleted."}},"required":["keep_id","merge_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"contact":{"type":"object","properties":{"id":{"type":"number"},"name":{"type":"string"},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"roles":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}}},"required":["id","name","email","phone","roles","tags"],"additionalProperties":false},"absorbed":{"type":"string","description":"Name of the contact that was merged in."}},"required":["contact","absorbed"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/review_contact_import":{"post":{"operationId":"review_contact_import","summary":"FIRST step of importing contacts from a CSV (from another CRM, a phone export, a spreadsheet)","description":"FIRST step of importing contacts from a CSV (from another CRM, a phone export, a spreadsheet). Use it when the user pastes or uploads CSV data. It parses the text, auto-maps the columns by header name (pass mapping to correct it, e.g. {\"Cell\": \"phone\"}), normalizes every row, flags probable duplicates against the existing rolodex, and stages everything under a batch id WITHOUT touching the contact list. Show the user the summary and, once they confirm, run import_contacts with the batch id. Nothing is imported until then. To import from their connected Gmail or Outlook account instead, use review_email_contacts.","tags":["contacts"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"csv_text":{"type":"string","minLength":1,"description":"The raw CSV content, headers in the first row."},"mapping":{"type":"object","additionalProperties":{"type":"string"},"description":"Column-to-field overrides, e.g. {\"Cell Phone\": \"phone\"}. Fields: name, first_name, last_name, email, phone, company, address, role, tags, notes, nmls_license, referral_source. Map a column to \"skip\" to ignore it."}},"required":["csv_text"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"batch_id":{"type":"string","description":"Pass this to import_contacts to execute the import."},"total_rows":{"type":"number"},"valid":{"type":"number"},"invalid":{"type":"number","description":"Rows without even a name, email or phone. Not staged."},"probable_duplicates":{"type":"number"},"mapping_used":{"type":"object","additionalProperties":{"type":"string"}},"unmapped_columns":{"type":"array","items":{"type":"string"}},"sample":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"duplicate_of":{"type":["string","null"]}},"required":["name","email","phone","duplicate_of"],"additionalProperties":false}}},"required":["batch_id","total_rows","valid","invalid","probable_duplicates","mapping_used","unmapped_columns","sample"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/review_email_contacts":{"post":{"operationId":"review_email_contacts","summary":"FIRST step of importing the user's contacts from their CONNECTED email account (Gmail or Outlook)","description":"FIRST step of importing the user's contacts from their CONNECTED email account (Gmail or Outlook). Use it when the user asks to import or sync their contacts from their email, Google or Outlook. It reads the address book, normalizes every entry, flags probable duplicates against the existing rolodex, and stages everything under a batch id WITHOUT touching the contact list. Show the user the summary and, once they confirm, run import_contacts with the batch id. Needs the email connected first (connect_email); both providers include contacts in that same authorization.","tags":["contacts"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"batch_id":{"type":"string","description":"Pass this to import_contacts to execute the import."},"total_found":{"type":"number"},"valid":{"type":"number"},"invalid":{"type":"number","description":"Entries without even a name, email or phone. Not staged."},"probable_duplicates":{"type":"number"},"truncated":{"type":"boolean","description":"True if the address book has more than 2000 entries and the rest were left out."},"sample":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"duplicate_of":{"type":["string","null"]}},"required":["name","email","phone","duplicate_of"],"additionalProperties":false}}},"required":["batch_id","total_found","valid","invalid","probable_duplicates","truncated","sample"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/import_contacts":{"post":{"operationId":"import_contacts","summary":"SECOND step of a contact import (CSV or connected email account): executes a batch that review_contact_import or review_email_contacts staged, after the user confirmed the summary","description":"SECOND step of a contact import (CSV or connected email account): executes a batch that review_contact_import or review_email_contacts staged, after the user confirmed the summary. Creates the clean rows, merges the exact duplicates (matching email/phone) into their existing contact, and discards rows flagged for review. Pass treat_duplicates_as if the user decided differently for the flagged ones: \"merge\" folds them into their closest match, \"create\" imports them as new contacts anyway, \"skip\" leaves them out (the default for name-only matches).","tags":["contacts"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"batch_id":{"type":"string","minLength":1,"description":"The batch id the review step returned."},"treat_duplicates_as":{"type":"string","enum":["merge","create","skip"],"description":"Override for rows flagged as probable duplicates. Default: each row's suggestion."}},"required":["batch_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"created":{"type":"number"},"merged":{"type":"number"},"discarded":{"type":"number"},"batch_id":{"type":"string"}},"required":["created","merged","discarded","batch_id"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/log_note":{"post":{"operationId":"log_note","summary":"Write a dated note on a contact's timeline","description":"Write a dated note on a contact's timeline. Use it whenever the user tells you something worth remembering about a person: \"note that Maria prefers texts after 5pm\", \"log that I called John about his rate\", or after a call/meeting they want recorded. The contact is whatever the user calls them — a name, email or phone fragment. Do NOT use it to change contact fields; that is save_contact.","tags":["contacts"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contact":{"type":"string","minLength":1,"description":"Who, as the user says it: name, email or phone fragment."},"note":{"type":"string","minLength":1,"maxLength":4000,"description":"The note text."},"date":{"type":"string","description":"When it happened (ISO date), only if different from today."}},"required":["contact","note"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"contact":{"type":"string"},"note":{"type":"string"},"logged_at":{"type":"string"}},"required":["contact","note","logged_at"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/set_communication_preferences":{"post":{"operationId":"set_communication_preferences","summary":"Turns email or SMS on or off for one person","description":"Turns email or SMS on or off for one person. Use it when someone asks not to be contacted on a channel (\"don't email the Garcias\", \"Maria only wants texts\"), or to undo it. Omitting a channel leaves it as it is. Note: a borrower who replies STOP to a text is opted out of SMS automatically and by law — do not turn that back on unless they ask.","tags":["contacts"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contacto":{"type":"string","minLength":1,"description":"Who, as the user says it: name, email or phone fragment."},"email":{"type":"boolean","description":"true = they accept email, false = do not email them."},"sms":{"type":"boolean","description":"true = they accept texts, false = do not text them."}},"required":["contacto"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"contacto":{"type":"string"},"email":{"type":"boolean"},"sms":{"type":"boolean"}},"required":["contacto","email","sms"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/set_contact_language":{"post":{"operationId":"set_contact_language","summary":"Sets the language a contact gets their messages in: English or Spanish","description":"Sets the language a contact gets their messages in: English or Spanish. Use it whenever the user says someone prefers Spanish (\"the Garcias speak Spanish\", \"write to Maria in Spanish\", \"send her everything in English from now on\"). Every template, reminder and automatic update to that person switches to that language from the next send on; messages already queued keep the language they were written in.","tags":["contacts"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contacto":{"type":"string","minLength":1,"description":"Who, as the user says it: name, email or phone fragment."},"language":{"type":"string","enum":["en","es"],"description":"'en' for English, 'es' for Spanish."}},"required":["contacto","language"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"contacto":{"type":"string"},"language":{"type":"string"},"language_label":{"type":"string"},"resumen":{"type":"string"}},"required":["contacto","language","language_label","resumen"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/delete_contact":{"post":{"operationId":"delete_contact","summary":"Permanently deletes a person from the rolodex","description":"Permanently deletes a person from the rolodex. Use it for duplicates or someone added by mistake. It refuses when the person is on a loan or pre-qual — merge them with merge_contacts instead. Ask the user to confirm before calling with confirmar: true.","tags":["contacts"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contacto":{"type":"string","minLength":1,"description":"Who, as the user says it: name, email or phone fragment."},"confirmar":{"type":"boolean","description":"Must be true to delete. Call with false first to check."}},"required":["contacto","confirmar"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"borrado":{"type":"boolean"},"nombre":{"type":"string"},"en_archivos":{"type":"number"}},"required":["borrado","nombre","en_archivos"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_pipeline":{"post":{"operationId":"view_pipeline","summary":"Shows the pipeline: how many pre-quals and loans sit in each stage, who they belong to, how many days each has been in its current stage, and which ones are stalled (more than 7 days in an active stage)","description":"Shows the pipeline: how many pre-quals and loans sit in each stage, who they belong to, how many days each has been in its current stage, and which ones are stalled (more than 7 days in an active stage). Use it when asked \"how does my pipeline look\", \"what do I have going on\", \"what is stuck\", or for any overview of current files. For one specific file use find_loan instead.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tipo":{"type":"string","enum":["prequal","loan","both"],"description":"Which pipeline to show. Use 'both' unless the user asks only about pre-quals or only about loans."},"estado":{"type":"string","description":"Only files in this stage, by its name as people say it ('clear to close', 'docs received'). Leave out for the whole pipeline."}},"required":["tipo"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"pipelines":{"type":"array","items":{"type":"object","properties":{"tipo":{"type":"string","enum":["prequal","loan"]},"total":{"type":"number"},"grupos":{"type":"array","items":{"type":"object","properties":{"estado":{"type":"string"},"label":{"type":"string"},"total":{"type":"number"},"prestamos":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"borrower":{"type":["string","null"]},"direccion":{"type":["string","null"]},"monto":{"type":["number","null"]},"dias_en_etapa":{"type":["number","null"]},"estancado":{"type":"boolean"}},"required":["id","borrower","direccion","monto","dias_en_etapa","estancado"],"additionalProperties":false}}},"required":["estado","label","total","prestamos"],"additionalProperties":false}}},"required":["tipo","total","grupos"],"additionalProperties":false}},"estancados":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"borrower":{"type":["string","null"]},"direccion":{"type":["string","null"]},"monto":{"type":["number","null"]},"dias_en_etapa":{"type":["number","null"]},"estancado":{"type":"boolean"},"estado":{"type":"string"}},"required":["id","borrower","direccion","monto","dias_en_etapa","estancado","estado"],"additionalProperties":false}}},"required":["pipelines","estancados"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/find_loan":{"post":{"operationId":"find_loan","summary":"Finds ONE loan or pre-qual and returns its full file: stage, amounts, property, borrowers, every other participant (realtor, title company…), the financial items collected so far, and the dated milestones","description":"Finds ONE loan or pre-qual and returns its full file: stage, amounts, property, borrowers, every other participant (realtor, title company…), the financial items collected so far, and the dated milestones. Use it whenever the user asks about a specific file — \"how is the Garcia loan going\", \"what do we have on 412 Elm St\", \"show me loan 1002941\". Accepts a borrower name, a property address, or a loan number.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string","minLength":1,"description":"The file, as the user says it: borrower name, property address, or loan number"}},"required":["prestamo"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"number"},"nombre":{"type":"string"},"tipo":{"type":"string","enum":["prequal","loan"]},"estado":{"type":"string"},"estado_label":{"type":"string"},"monto":{"type":["number","null"]},"fecha_cierre":{"type":["string","null"]},"etiquetas":{"type":"array","items":{"type":"string"}},"datos":{"type":"object","additionalProperties":{}},"calculados":{"type":"object","additionalProperties":{"type":"number"}},"borrowers":{"type":"array","items":{"type":"object","properties":{"orden":{"type":"number"},"rol":{"type":"string"},"nombre":{"type":"string"},"email":{"type":["string","null"]},"telefono":{"type":["string","null"]}},"required":["orden","rol","nombre","email","telefono"],"additionalProperties":false}},"participantes":{"type":"array","items":{"type":"object","properties":{"rol":{"type":"string"},"nombre":{"type":"string"},"email":{"type":["string","null"]},"telefono":{"type":["string","null"]}},"required":["rol","nombre","email","telefono"],"additionalProperties":false}},"items":{"type":"array","items":{"type":"object","properties":{"grupo":{"type":"string"},"tipo":{"type":"string"},"label":{"type":"string"},"datos":{"type":"object","additionalProperties":{}}},"required":["grupo","tipo","label","datos"],"additionalProperties":false}},"hitos":{"type":"array","items":{"type":"object","properties":{"hito":{"type":"string"},"label":{"type":"string"},"fecha":{"type":"string"}},"required":["hito","label","fecha"],"additionalProperties":false}}},"required":["id","nombre","tipo","estado","estado_label","monto","fecha_cierre","etiquetas","datos","calculados","borrowers","participantes","items","hitos"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/create_prequal":{"post":{"operationId":"create_prequal","summary":"Creates a new pre-qualification for a borrower and puts it in the pipeline at \"To Be Contacted\"","description":"Creates a new pre-qualification for a borrower and puts it in the pipeline at \"To Be Contacted\". Use it when a new lead comes in: \"add a pre-qual for Maria Garcia\", \"new lead: John Smith, 555-0142\". If the borrower is not a contact yet, pass their email or phone and the contact is created too. It also takes co-borrowers (up to 3 besides the main borrower: \"a pre-qual for the Hassans, Ali and Noor\"), priority, notes and who referred the lead. To move an existing file forward use move_stage, not this; to add a co-borrower to a file that already exists use assign_loan_contact.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"borrower":{"type":"string","minLength":2,"description":"The borrower, as the user says it: a name, an email, or a phone"},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"description":"Email for a NEW contact. Null if the borrower already exists or no email was given."},"telefono":{"type":["string","null"],"description":"Phone for a NEW contact. Null if the borrower already exists or no phone was given."},"co_borrowers":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"nombre":{"type":"string","minLength":2,"description":"The co-borrower, as the user says it: a name, an email, or a phone"},"email":{"type":["string","null"],"description":"Email for a NEW co-borrower. Null if they already exist or none was given."},"telefono":{"type":["string","null"],"description":"Phone for a NEW co-borrower. Null if they already exist or none was given."}},"required":["nombre","email","telefono"],"additionalProperties":false},"maxItems":3},{"type":"null"}],"description":"The other borrowers on the file — a spouse, a partner. Up to 3 besides the main one. Null if there is only one borrower."},"prioridad":{"anyOf":[{"type":"string","enum":["low","medium","high"]},{"type":"null"}],"description":"How hot the lead is, if the user said it. Null otherwise."},"notas":{"type":["string","null"],"description":"Notes about the lead, if the user gave any. Null otherwise."},"referral":{"type":["string","null"],"description":"Who referred the lead, as the user says it (an existing contact). Null if not mentioned."}},"required":["borrower","email","telefono","co_borrowers","prioridad","notas","referral"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"number"},"borrower":{"type":"string"},"estado":{"type":"string"},"contacto_creado":{"type":"boolean"},"co_borrowers":{"type":"array","items":{"type":"string"}}},"required":["id","borrower","estado","contacto_creado","co_borrowers"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/prequal_form":{"post":{"operationId":"prequal_form","summary":"Opens an interactive pre-qualification form right in the chat, pre-filled with whatever the user already said","description":"Opens an interactive pre-qualification form right in the chat, pre-filled with whatever the user already said. Use it when the user wants to create a pre-qual but has not given the details yet (\"I want to create a pre-qual\", \"new lead\"), or asks for a form. If they already gave borrower plus phone or email, call create_prequal directly instead — do not make them fill a form they already dictated.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"nombre":{"type":["string","null"],"description":"The borrower's name, if the user already said it. Null otherwise."},"email":{"type":["string","null"],"description":"The borrower's email, if given. Null otherwise."},"telefono":{"type":["string","null"],"description":"The borrower's phone, if given. Null otherwise."},"prioridad":{"anyOf":[{"type":"string","enum":["low","medium","high"]},{"type":"null"}],"description":"Priority, if the user said it. Null otherwise."},"notas":{"type":["string","null"],"description":"Notes about the lead, if any. Null otherwise."}},"required":["nombre","email","telefono","prioridad","notas"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"nombre":{"type":["string","null"]},"email":{"type":["string","null"]},"telefono":{"type":["string","null"]},"prioridad":{"anyOf":[{"type":"string","enum":["low","medium","high"]},{"type":"null"}]},"notas":{"type":["string","null"]}},"required":["nombre","email","telefono","prioridad","notas"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/create_loan":{"post":{"operationId":"create_loan","summary":"Creates a loan directly in the pipeline, without going through a pre-qualification","description":"Creates a loan directly in the pipeline, without going through a pre-qualification. Use it when a deal arrives already under contract: \"create a loan for Robert Miller\", \"new loan: the Hassans, contract received\". Takes up to 4 borrowers (the first is the main one), the initial status (defaults to Contract Received), notes, and the selling agent, listing agent or referral. If the borrower is not a contact yet, pass their email or phone and the contact is created too. To turn an existing pre-qual into a loan use convert_to_loan, not this.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"borrower":{"type":"string","minLength":2,"description":"The main borrower, as the user says it: a name, an email, or a phone"},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"description":"Email for a NEW contact. Null if the borrower already exists or no email was given."},"telefono":{"type":["string","null"],"description":"Phone for a NEW contact. Null if the borrower already exists or no phone was given."},"co_borrowers":{"anyOf":[{"type":"array","items":{"type":"string"},"maxItems":3},{"type":"null"}],"description":"Co-borrowers, as the user says them (existing contacts). Null if none."},"estado":{"type":["string","null"],"description":"Initial loan status value if the user said one (e.g. \"contract_received\", \"qualification\"). Null for the default (Contract Received)."},"notas":{"type":["string","null"],"description":"Notes about the loan, if the user gave any. Null otherwise."},"selling_agent":{"type":["string","null"],"description":"The selling agent, as the user says it (an existing contact). Null if not mentioned."},"listing_agent":{"type":["string","null"],"description":"The listing agent, as the user says it (an existing contact). Null if not mentioned."},"referral":{"type":["string","null"],"description":"Who referred the deal, as the user says it (an existing contact). Null if not mentioned."}},"required":["borrower","email","telefono","co_borrowers","estado","notas","selling_agent","listing_agent","referral"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"number"},"borrower":{"type":"string"},"estado":{"type":"string"},"contactos_creados":{"type":"number"}},"required":["id","borrower","estado","contactos_creados"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/loan_form":{"post":{"operationId":"loan_form","summary":"Opens an interactive loan creation form right in the chat, pre-filled with whatever the user already said","description":"Opens an interactive loan creation form right in the chat, pre-filled with whatever the user already said. Use it when the user wants to create a loan but has not given the details yet (\"I want to create a loan\", \"new loan\"), or asks for a form. If they already gave the borrower plus phone or email, call create_loan directly instead — do not make them fill a form they already dictated.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"nombre":{"type":["string","null"],"description":"The borrower's name, if the user already said it. Null otherwise."},"email":{"type":["string","null"],"description":"The borrower's email, if given. Null otherwise."},"telefono":{"type":["string","null"],"description":"The borrower's phone, if given. Null otherwise."},"estado":{"type":["string","null"],"description":"Initial loan status value, if the user said one. Null otherwise."},"notas":{"type":["string","null"],"description":"Notes about the loan, if any. Null otherwise."}},"required":["nombre","email","telefono","estado","notas"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"nombre":{"type":["string","null"]},"email":{"type":["string","null"]},"telefono":{"type":["string","null"]},"estado":{"type":["string","null"]},"notas":{"type":["string","null"]}},"required":["nombre","email","telefono","estado","notas"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/contact_form":{"post":{"operationId":"contact_form","summary":"Opens an interactive new-contact form right in the chat, pre-filled with whatever the user already said","description":"Opens an interactive new-contact form right in the chat, pre-filled with whatever the user already said. Use it when the user wants to add a contact but has not given the details yet (\"add a new realtor\", \"I met someone new\"), or asks for a form. If they already dictated name plus email or phone, call save_contact directly instead.","tags":["contactos"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"nombre":{"type":["string","null"],"description":"The contact's name, if already said. Null otherwise."},"email":{"type":["string","null"],"description":"Email, if given. Null otherwise."},"telefono":{"type":["string","null"],"description":"Phone, if given. Null otherwise."},"rol":{"type":["string","null"],"description":"Their role slug if the user said it (e.g. 'borrower', 'listing_agent', 'referral_source'). Null otherwise."},"empresa":{"type":["string","null"],"description":"Company, if given. Null otherwise."},"notas":{"type":["string","null"],"description":"Notes, if any. Null otherwise."}},"required":["nombre","email","telefono","rol","empresa","notas"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"nombre":{"type":["string","null"]},"email":{"type":["string","null"]},"telefono":{"type":["string","null"]},"rol":{"type":["string","null"]},"empresa":{"type":["string","null"]},"notas":{"type":["string","null"]}},"required":["nombre","email","telefono","rol","empresa","notas"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/check_duplicates":{"post":{"operationId":"check_duplicates","summary":"Checks whether a person is already in the rolodex before creating them, and says WHY each match looks like the same person (same email, same phone, similar name) with a 0-1 score","description":"Checks whether a person is already in the rolodex before creating them, and says WHY each match looks like the same person (same email, same phone, similar name) with a 0-1 score. Use it when the user is about to add someone and you want to avoid a second copy, when they ask \"do I already have X?\", or before create_prequal / create_loan with a brand-new borrower. To create despite the matches, call save_contact with create_anyway.","tags":["contactos"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"nombre":{"type":"string","description":"The name to check. Required."},"email":{"type":["string","null"],"description":"Email, if known. Null otherwise."},"telefono":{"type":["string","null"],"description":"Phone, if known. Null otherwise."},"solo_probables":{"type":"boolean","description":"When true, only matches at or above 0.8 (almost certainly the same person). When false, also returns weaker name matches worth a look."}},"required":["nombre","email","telefono","solo_probables"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"duplicates":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"name":{"type":"string"},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"company":{"type":["string","null"]},"roles":{"type":"array","items":{"type":"string"},"maxItems":50},"roles_label":{"type":"array","items":{"type":"string"},"maxItems":50},"score":{"type":"number"},"reason":{"type":"string","enum":["same_email","same_phone","similar_name"]}},"required":["id","name","email","phone","company","roles","roles_label","score","reason"],"additionalProperties":false},"description":"Ordered from the surest match down. Empty means nobody like this exists yet."}},"required":["duplicates"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/task_form":{"post":{"operationId":"task_form","summary":"Opens an interactive new-task form right in the chat, pre-filled with whatever the user already said","description":"Opens an interactive new-task form right in the chat, pre-filled with whatever the user already said. Use it when the user wants to add a task but has not given the details yet (\"add a task\", \"remind me of something\"), or asks for a form. If they already dictated the task (\"remind me to call Maria tomorrow\"), call save_task directly instead.","tags":["tareas"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"titulo":{"type":["string","null"],"description":"The task title, if already said. Null otherwise."},"vence":{"type":["string","null"],"description":"Due date in YYYY-MM-DD, if said. Null otherwise."},"descripcion":{"type":["string","null"],"description":"Longer detail, if any. Null otherwise."},"prestamo":{"type":["string","null"],"description":"The loan or borrower this task is about, as the user says it. Null otherwise."},"asignado":{"type":["string","null"],"description":"Who it is for, if said: a teammate name or email, or \"me\". Null otherwise."},"seccion":{"type":["string","null"],"description":"Which section of the task list it goes into, if said. Null otherwise."}},"required":["titulo","vence","descripcion","prestamo","asignado","seccion"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"titulo":{"type":["string","null"]},"vence":{"type":["string","null"]},"descripcion":{"type":["string","null"]},"prestamo":{"type":["string","null"]},"asignado":{"type":["string","null"]},"seccion":{"type":["string","null"]}},"required":["titulo","vence","descripcion","prestamo","asignado","seccion"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/check_missing_data":{"post":{"operationId":"check_missing_data","summary":"Shows what DATA is still missing on a file — not documents, but facts: FICO score, income on file, purchase price, loan program, closing date… — for the current stage and the next one, with who owes each (borrower, loan officer, realtor, lender, title)","description":"Shows what DATA is still missing on a file — not documents, but facts: FICO score, income on file, purchase price, loan program, closing date… — for the current stage and the next one, with who owes each (borrower, loan officer, realtor, lender, title). Use it for \"what is missing on the Garcia file?\", \"is this file ready to move?\", \"what do I need before review?\". For missing DOCUMENTS use check_documents.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"loan":{"type":"string","minLength":1,"description":"The file, as the user says it: borrower name, address, or number"}},"required":["loan"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string"},"estado":{"type":"string"},"faltantes":{"type":"array","items":{"type":"object","properties":{"dato":{"type":"string"},"responsable":{"type":"string"},"para_etapa":{"type":"string"},"ya_se_debe":{"type":"boolean"}},"required":["dato","responsable","para_etapa","ya_se_debe"],"additionalProperties":false}},"resumen":{"type":"string"}},"required":["prestamo","estado","faltantes","resumen"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/move_stage":{"post":{"operationId":"move_stage","summary":"Moves one or several loans or pre-quals to another pipeline stage and stamps the milestone date","description":"Moves one or several loans or pre-quals to another pipeline stage and stamps the milestone date. Use it whenever the user reports progress: \"the Garcia docs came in\" → docs_received, \"submitted Elm St to underwriting\" → submitted_to_underwriting, \"put it on hold\", or a batch at once (pass them in prestamos) — with several it moves as many as it can and reports which ones failed, one by one. Some transitions notify the borrower or the realtor automatically: with preview=true it only answers WHAT would be sent, without moving anything — use that when the user asks \"what happens if…\". With skip_notifications=true it moves silently (e.g. correcting a mistake). The valid stages depend on whether the file is a pre-qual or a loan; if unsure, view_pipeline lists them. To turn a pre-qual into a loan use convert_to_loan instead.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string","minLength":1,"description":"The file, as the user says it: borrower name, property address, or loan number. For several files use prestamos instead."},"prestamos":{"type":"array","items":{"type":"string","minLength":1},"description":"Several files at once, each one as the user says it. They all move to the same stage."},"estado":{"type":"string","minLength":1,"description":"The target stage, in snake_case: 'docs_received', 'clear_to_close'…"},"preview":{"type":"boolean","description":"true = do NOT move; only report which notifications the move would send. For \"what would happen if…\" questions."},"skip_notifications":{"type":"boolean","description":"true = move without notifying anyone. For silent corrections."},"force":{"type":"boolean","description":"true = advance EVEN IF documents or UW conditions are still owed. Only when the user explicitly says to move it anyway."}},"required":["estado"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":["number","null"]},"nombre":{"type":["string","null"]},"de":{"type":["string","null"]},"a":{"type":"string"},"a_label":{"type":"string"},"solo_preview":{"type":"boolean"},"notificaciones_previstas":{"type":"array","items":{"type":"string"}},"datos_faltantes":{"type":"array","items":{"type":"string"}},"movidos":{"type":"number"},"resultados":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"nombre":{"type":"string"},"de":{"type":"string"},"a":{"type":"string"}},"required":["id","nombre","de","a"],"additionalProperties":false}},"fallidos":{"type":"array","items":{"type":"object","properties":{"prestamo":{"type":"string"},"error":{"type":"string"}},"required":["prestamo","error"],"additionalProperties":false}}},"required":["id","nombre","de","a","a_label","solo_preview","notificaciones_previstas","datos_faltantes","movidos","resultados","fallidos"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/convert_to_loan":{"post":{"operationId":"convert_to_loan","summary":"Converts a pre-qualification into an active loan, starting it at \"Contract Received\"","description":"Converts a pre-qualification into an active loan, starting it at \"Contract Received\". Use it when a pre-qual borrower goes under contract: \"Garcia got the house!\", \"we have a contract on Elm St\", \"convert Maria to a loan\". Only works on pre-quals; an active loan moves with move_stage.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string","minLength":1,"description":"The pre-qual, as the user says it: borrower name, address, or number"}},"required":["prestamo"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"number"},"nombre":{"type":"string"},"estado":{"type":"string"}},"required":["id","nombre","estado"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/update_loan":{"post":{"operationId":"update_loan","summary":"Updates fields on a loan or pre-qual: amount, closing date, tags, file data (address, program, purchase price, down payment, FICO, DTI, rates and lock terms, appraisal status/value, income verification, notes…) or a milestone date (disclosures, lock, appraisal, title, insurance, credit, contract, closing)","description":"Updates fields on a loan or pre-qual: amount, closing date, tags, file data (address, program, purchase price, down payment, FICO, DTI, rates and lock terms, appraisal status/value, income verification, notes…) or a milestone date (disclosures, lock, appraisal, title, insurance, credit, contract, closing). Use it when the user gives new facts about a file: \"the Garcia loan is for $420k\", \"rate locked at 6.5 until Sep 15\", \"appraisal came in at $510k\", \"closing moved to October 3\", \"tag it VIP\". Only pass what changed. To change the pipeline stage use move_stage instead.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string","minLength":1,"description":"The file, as the user says it: borrower name, address, or number"},"monto":{"anyOf":[{"type":"number","minimum":0},{"type":"null"}],"description":"New loan amount in USD. Null if unchanged."},"fecha_cierre":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"description":"New closing date (ISO). Null if unchanged."},"etiquetas":{"anyOf":[{"type":"array","items":{"type":"string","minLength":1},"maxItems":50},{"type":"null"}],"description":"The FULL new list of tags (replaces). Null if unchanged."},"datos":{"anyOf":[{"type":"object","properties":{"property_address":{"type":"string","maxLength":300,"description":"Street address of the subject property"},"property_type":{"type":"string","enum":["single_family","condo","townhouse","multi_family","manufactured","duplex","triplex","fourplex","pud","condotel","commercial","land"],"description":"What kind of property it is"},"property_usage":{"type":"string","enum":["primary_residence","second_home","investment"],"description":"How the borrower will use the property"},"property_value":{"type":"number","minimum":0,"description":"Appraised or estimated value in USD"},"structure_type":{"type":"string","enum":["attached","detached"],"description":"Whether the structure is attached or detached"},"appraisal_status":{"type":"string","enum":["not_ordered","ordered","scheduled","inspected","received","received_for_value","received_under_value","received_subject_to","waived"],"description":"Where the appraisal stands, from not ordered to received or waived"},"appraisal_value":{"type":"number","minimum":0,"description":"Appraised value of the property in USD, once the report is in"},"title_under_a_corporation":{"type":"boolean","description":"Whether the property title is held under a corporation or LLC"},"corporate_name":{"type":"string","maxLength":200,"description":"Name of the corporation or LLC holding title, if any"},"number_of_units":{"type":"integer","minimum":1,"maximum":100,"description":"Number of units in the property (1 for single family)"},"loan_number":{"type":"string","maxLength":60,"description":"Loan number assigned by the broker"},"loan_number_lender":{"type":"string","maxLength":60,"description":"Loan number assigned by the lender (loan_number is the broker one)"},"program":{"type":"string","enum":["conventional","fha","va","usda","jumbo","non_qm","heloc","heloan","reverse","private","foreign_national"],"description":"Loan program"},"purpose":{"type":"string","enum":["purchase","refinance","cash_out","rate_term_refinance","limited_cash_out"],"description":"Purchase, refinance, cash-out, rate/term refinance, or limited cash-out"},"fico_score":{"type":"integer","minimum":300,"maximum":850,"description":"Qualifying FICO credit score of the borrower"},"loan_amount_2":{"type":"number","minimum":0,"description":"Second mortgage amount in USD, for combo/piggyback loans"},"purchase_price":{"type":"number","minimum":0,"description":"Price paid to purchase the property in USD"},"down_payment":{"type":"number","minimum":0,"description":"Down payment in USD"},"down_payment_percentage":{"type":"number","minimum":0,"maximum":100,"description":"Down payment as a percent of the purchase price"},"loan_to_value":{"type":"number","minimum":0,"maximum":200,"description":"Loan-to-value ratio, in percent"},"combined_loan_to_value":{"type":"number","minimum":0,"maximum":200,"description":"Combined loan-to-value ratio (all liens), in percent"},"dti":{"type":"string","maxLength":20,"description":"Debt-to-income ratio, like \"43\" or \"31/43\" (front/back)"},"amortization_type":{"type":"string","enum":["fixed","adjustable"],"description":"Fixed or adjustable rate amortization"},"amortization_term_months":{"type":"number","enum":[360,240,180],"description":"Amortization term in months: 360, 240, or 180"},"interest_only":{"type":"boolean","description":"Whether the loan has an interest-only period"},"income_verification":{"type":"string","enum":["full_docs","form_1099","bank_statement","dscr","no_ratio","no_income","profit_and_loss","voe","asset_depletion"],"description":"How income is verified: full docs, 1099, bank statements, DSCR, no ratio, no income, P&L, VOE, or asset depletion"},"waive_escrows":{"type":"boolean","description":"Whether the borrower waives the escrow account"},"lender_program":{"type":"string","maxLength":120,"description":"The lender's own program or product name"},"lender_name":{"type":"string","maxLength":120,"description":"The lender the file is with, like \"FREEDOM\" or \"UWM\""},"loan_term":{"type":"integer","minimum":1,"maximum":600,"description":"Loan term in months, like 360 for a 30-year. Separate from amortization_term_months, which only takes 360, 240 or 180."},"prepayment_penalty_term":{"type":"string","maxLength":60,"description":"Prepayment penalty term, like \"3 years\""},"prepayment_penalty_type":{"type":"string","maxLength":120,"description":"Prepayment penalty structure, like \"5/4/3/2/1\" or \"fixed 5%\""},"rate":{"type":"number","minimum":0,"maximum":30,"description":"Interest rate, in percent"},"note_rate":{"type":"number","minimum":0,"maximum":30,"description":"The note rate on the mortgage, in percent"},"interest_rate_locked":{"type":"boolean","description":"Whether the interest rate is locked"},"interest_rate_disclosed":{"type":"number","minimum":0,"maximum":30,"description":"Interest rate disclosed to the borrower, in percent"},"interest_rate_buydown":{"type":"number","description":"Rate buydown, in percent"},"interest_rate_cost":{"type":"number","description":"Cost of the interest rate, in points (percent of loan amount)"},"lock_cost":{"type":"number","description":"Cost of the rate lock in USD"},"rate_cost_credit":{"type":"number","description":"Rate cost or credit amount in USD (negative means a credit)"},"credit_to_borrower":{"type":"number","description":"Credit applied to the borrower in USD"},"interest_rate_cost_credit_percent":{"type":"number","description":"Rate cost or credit, as a percent of the loan amount"},"rate_lock_extension_cost":{"type":"number","description":"Cost of extending the rate lock, in percent"},"rate_locked_type":{"type":"string","enum":["borrower_paid","lender_paid"],"description":"Whether the locked rate compensation is borrower-paid or lender-paid"},"lock_expires":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"When the rate lock expires (ISO date)"},"lock_status":{"type":"string","maxLength":40,"description":"Where the rate lock stands, in the words of the LOS: \"Locked\", \"Floating\", \"Lock Requested\", \"Expired\". interest_rate_locked is the plain yes/no version."},"compensation_pct":{"type":"number","minimum":0,"maximum":10,"description":"Broker compensation, in percent of the loan amount"},"priority":{"type":"string","enum":["low","medium","high"],"description":"How hot the file is, to sort the pipeline"},"processor_name":{"type":"string","maxLength":120,"description":"Name of the processor working the file"},"processor_email":{"type":"string","maxLength":200,"description":"Email of the processor working the file"},"processor_phone":{"type":"string","maxLength":40,"description":"Phone of the processor working the file"},"originator_name":{"type":"string","maxLength":120,"description":"Name of the loan originator who owns the file"},"originator_email":{"type":"string","maxLength":200,"description":"Email of the loan originator who owns the file"},"originator_phone":{"type":"string","maxLength":40,"description":"Phone of the loan originator who owns the file"},"lead_source":{"type":"string","maxLength":120,"description":"How the file came in, like \"Referral - Business Contact\""},"referral_source_name":{"type":"string","maxLength":120,"description":"Who referred the borrower"},"referral_source_email":{"type":"string","maxLength":200,"description":"Email of whoever referred the borrower"},"referral_source_phone":{"type":"string","maxLength":40,"description":"Phone of whoever referred the borrower"},"referral_source_company":{"type":"string","maxLength":160,"description":"Company of whoever referred the borrower"},"archived":{"type":"boolean","description":"Whether the file is archived — put away, not worked on. This is NOT the same as a closed or cancelled stage: an archived file keeps whatever stage it had."},"archived_date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"When the file was archived (ISO date)"},"notes":{"type":"string","maxLength":4000,"description":"Free-form notes about the file"},"custom_dates":{"type":"object","additionalProperties":{"type":"string","minLength":1,"maxLength":60},"description":"Names of the custom milestone dates on this file, keyed by their custom_… key. Do not write this directly: use set_custom_date to create, rename or remove a custom date."}},"additionalProperties":false},{"type":"null"}],"description":"File data to merge in. Property: property_address, property_type, property_usage, property_value, structure_type, appraisal_status, appraisal_value, number_of_units, title_under_a_corporation, corporate_name. Loan: loan_number (broker), loan_number_lender, program, purpose, fico_score, purchase_price, down_payment, down_payment_percentage, loan_amount_2, loan_to_value, combined_loan_to_value, dti, amortization_type, amortization_term_months, interest_only, income_verification, waive_escrows, lender_program, prepayment_penalty_term/type. Rates: rate, note_rate, interest_rate_locked, interest_rate_disclosed, interest_rate_buydown, interest_rate_cost, lock_cost, rate_cost_credit, credit_to_borrower, interest_rate_cost_credit_percent, rate_lock_extension_cost, rate_locked_type, lock_expires. Also compensation_pct, priority, notes. Null if unchanged."},"fechas":{"anyOf":[{"type":"object","additionalProperties":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"type":"null"}],"description":"Milestone dates to set by name, like {\"appraisal_received\": \"2026-09-01\"}. Valid names are pipeline stages — which is also how you CORRECT the day a file entered a stage without moving it again: contract_received, loan_setup, loan_disclosed, disclosures_signed, preparing_to_submit_to_uw, submitted_to_processing, submitted_to_underwriting, approved_with_conditions, resubmitted_to_processing, submitted_for_clear_to_close, clear_to_close, docs_out, docs_signed, on_hold, loan_closed, loan_cancelled, loan_archived, loan_suspended, loan_funded, commission_paid — and named milestones — disclosures: initial_disclosures_sent, intent_to_proceed, le_sent, le_signed, le_received, revised_le_sent, revised_le_signed, cd_requested, cd_sent, cd_signed, revised_cd_sent, revised_cd_signed; lock: lock_date, lock_extension, lock_expiration; appraisal: appraisal_ordered, appraisal_received; title: title_ordered, title_received; insurance: hoi_ordered, hoi_received; credit: credit_ordered, credit_expiration, tax_transcript_ordered, tax_transcript_received; processing: processing, submitted_to_clear_conditions; contract: sales_contract_date, appraisal_contingency, loan_contingency, closing_contingency, earliest_closing; closing: estimated_funding, first_payment, date_to_avoid_epo, pre_approval_expiry, underwriting_approval. A custom date this file already has (its key starts with custom_) also works here; to create, rename or remove one use set_custom_date. Null if unchanged."}},"required":["prestamo","monto","fecha_cierre","etiquetas","datos","fechas"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"number"},"nombre":{"type":"string"},"cambios":{"type":"object","additionalProperties":{}}},"required":["id","nombre","cambios"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/set_custom_date":{"post":{"operationId":"set_custom_date","summary":"Creates, renames, dates or removes a date the loan file needs but the standard milestone catalog does not have","description":"Creates, renames, dates or removes a date the loan file needs but the standard milestone catalog does not have. Use it when the user names a deadline that is not one of the built-in milestones: \"add an HOA docs due date of Sep 15 to the Garcia file\", \"rename that date to Attorney Review\", \"drop the HOA docs date\". For a milestone that already exists in the catalog (appraisal, lock, CD, closing…) use update_loan instead. To move a custom date that already exists, either tool works.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string","minLength":1,"description":"The file, as the user says it: borrower name, address, or number"},"nombre":{"type":"string","minLength":1,"maxLength":60,"description":"What the date is called, as the user says it (\"HOA docs due\"). For an existing custom date, words from its name are enough."},"fecha":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"description":"The date itself (ISO). Null to create or rename without dating it yet."},"renombrar":{"anyOf":[{"type":"string","minLength":1,"maxLength":60},{"type":"null"}],"description":"The new name, when the user is renaming an existing one."},"quitar":{"type":["boolean","null"],"description":"True to remove the custom date from the file entirely."}},"required":["prestamo","nombre"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"number"},"prestamo":{"type":"string"},"clave":{"type":"string"},"nombre":{"type":"string"},"fecha":{"type":["string","null"]},"quitada":{"type":"boolean"}},"required":["id","prestamo","clave","nombre","fecha","quitada"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/update_loan_items":{"post":{"operationId":"update_loan_items","summary":"Records or removes financial items and properties on a loan file: jobs (W2, 1099, VOE), other income (P&L, child support, IRS transcripts), bank accounts, retirement accounts, gift funds, asset depletion, liabilities (incl","description":"Records or removes financial items and properties on a loan file: jobs (W2, 1099, VOE), other income (P&L, child support, IRS transcripts), bank accounts, retirement accounts, gift funds, asset depletion, liabilities (incl. IRS or government payment plans, child support payments), and real estate. Use it whenever the loan officer mentions a borrower's income, assets, debts, or properties — e.g. \"she works at Amazon, W2, $85k\" or \"they have a checking account at Chase\". For a property, proposed_status in datos says if it is retained, pending sale, or sold with this loan. The document checklist regenerates automatically from these items.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string","minLength":1,"description":"The file, as the user says it: borrower name, address, or number"},"agregar":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"tipo":{"type":"string","enum":["w2","form_1099","paystub","tax_return_personal","tax_return_business","social_security","pension","alimony","rental_income","voe","irs_transcripts","profit_and_loss_income","child_support","bank_statement_personal","bank_statement_business","retirement_401k","brokerage","cd","gift_funds","sale_proceeds","heloc","crypto","asset_depletion","sale_of_assets","mortgage","heloc_balance","auto_loan","student_loan","credit_card","alimony_owed","child_support_payments","irs_payment_plan","third_party_payments","retirement_draw_payment","government_payment_plan","other_liability","primary_residence","second_home","investment_property","current_residence","pending_sale"],"description":"Item type from the catalog, like w2, form_1099, paystub, voe, irs_transcripts, profit_and_loss_income, child_support, bank_statement_personal, retirement_401k, gift_funds, asset_depletion, sale_of_assets, child_support_payments, irs_payment_plan, third_party_payments, retirement_draw_payment, government_payment_plan, primary_residence, investment_property…"},"participante":{"type":["string","null"],"description":"Whose item it is, by name. Null = the main borrower."},"datos":{"anyOf":[{"type":"object","properties":{"amount":{"type":"number","description":"Monthly amount, balance, or value in USD"},"institution":{"type":"string","maxLength":200,"description":"Bank, employer, or servicer"},"address":{"type":"string","maxLength":300,"description":"Property address, for property items"},"proposed_status":{"type":"string","enum":["retained","pending_sale","sold"],"description":"For property items only: what happens to the property with this loan — kept (retained), listed (pending_sale), or sold"},"description":{"type":"string","maxLength":1000,"description":"Anything else worth noting"}},"additionalProperties":false},{"type":"null"}],"description":"Details: employer, bank, balance, annual_income, address, and for properties proposed_status (retained, pending_sale, sold). Null if none were given."}},"required":["tipo","participante","datos"],"additionalProperties":false}},{"type":"null"}],"description":"Items to add. Null if only removing."},"editar":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"description":"The item id to change."},"datos":{"type":"object","properties":{"amount":{"type":"number","description":"Monthly amount, balance, or value in USD"},"institution":{"type":"string","maxLength":200,"description":"Bank, employer, or servicer"},"address":{"type":"string","maxLength":300,"description":"Property address, for property items"},"proposed_status":{"type":"string","enum":["retained","pending_sale","sold"],"description":"For property items only: what happens to the property with this loan — kept (retained), listed (pending_sale), or sold"},"description":{"type":"string","maxLength":1000,"description":"Anything else worth noting"}},"additionalProperties":false,"description":"Only the fields that change; the rest of the item stays as it was. Send an empty string to clear a text field."}},"required":["id","datos"],"additionalProperties":false}},{"type":"null"}],"description":"Corrections to items already on file — a mistyped amount, the employer that was missing. Null if not correcting anything. ALWAYS prefer this over removing and re-adding: removing an item takes its documents with it."},"quitar":{"anyOf":[{"type":"array","items":{"type":"integer","exclusiveMinimum":0}},{"type":"null"}],"description":"Item ids to remove (find_loan lists them). Null if only adding."}},"required":["prestamo","agregar","editar","quitar"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string"},"agregados":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"item":{"type":"string"}},"required":["id","item"],"additionalProperties":false}},"editados":{"type":"number"},"quitados":{"type":"number"},"checklist":{"type":"object","properties":{"nuevos_documentos":{"type":"number"},"ya_no_aplican":{"type":"number"}},"required":["nuevos_documentos","ya_no_aplican"],"additionalProperties":false}},"required":["prestamo","agregados","editados","quitados","checklist"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/archive_loan":{"post":{"operationId":"archive_loan","summary":"Archives one or several loans or pre-quals, or brings archived ones back into the pipeline","description":"Archives one or several loans or pre-quals, or brings archived ones back into the pipeline. Modellus already hides files with no activity for 39 days — use this to disagree with that in either direction: \"bring the Garcia loan back\", \"unarchive 412 Elm\", \"this one is still alive\", \"archive the Miller file, it is dead\", or a whole batch at once (pass them in prestamos). With several it archives as many as it can and reports which ones failed, one by one. Your choice sticks: the automatic rule will not undo it. This is NOT how you close a loan — to record that a loan closed or was cancelled use move_stage.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string","minLength":1,"description":"The file, as the user says it: borrower name, property address, or loan number. For several files use prestamos instead."},"prestamos":{"type":"array","items":{"type":"string","minLength":1},"description":"Several files at once, each one as the user says it. Use it when they clear out a batch (\"archive all of these\")."},"archivar":{"type":"boolean","description":"True takes them out of the pipeline, false brings them back."}},"required":["archivar"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":["number","null"]},"nombre":{"type":["string","null"]},"archivado":{"type":"boolean"},"archivados":{"type":"number"},"resultados":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"nombre":{"type":"string"}},"required":["id","nombre"],"additionalProperties":false}},"fallidos":{"type":"array","items":{"type":"object","properties":{"prestamo":{"type":"string"},"error":{"type":"string"}},"required":["prestamo","error"],"additionalProperties":false}}},"required":["id","nombre","archivado","archivados","resultados","fallidos"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/assign_loan_contact":{"post":{"operationId":"assign_loan_contact","summary":"Adds or removes a participant on a loan or pre-qual by role: borrowers and co-borrowers (up to 4, ordered), realtor, listing agent, title company, appraiser, insurance company, account executive, HOA office, developer, or the file's alt contact — the extra person to call about it, with no particular role","description":"Adds or removes a participant on a loan or pre-qual by role: borrowers and co-borrowers (up to 4, ordered), realtor, listing agent, title company, appraiser, insurance company, account executive, HOA office, developer, or the file's alt contact — the extra person to call about it, with no particular role. Use it when the user connects people to a file: \"the realtor on Elm St is Susan Chen\", \"add Carlos as co-borrower on the Garcia loan\", \"the second number on 412 Elm is his daughter\", \"remove the appraiser from 412 Elm\". The person is usually already a contact; if they are not, pass their email or phone and the contact is created and attached in one go.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string","minLength":1,"description":"The file, as the user says it: borrower name, address, or number"},"contacto":{"type":"string","minLength":1,"description":"The person, as the user says it: name, email, or phone"},"rol":{"type":"string","minLength":1,"description":"Their role on this file, in snake_case: 'borrower', 'co_borrower', 'realtor', 'title_company', 'appraiser'…"},"accion":{"type":"string","enum":["add","remove"],"description":"'add' to attach the person, 'remove' to detach them"},"email":{"type":["string","null"],"description":"Email for a NEW contact, when adding someone who is not in the rolodex yet. Null if they already exist or none was given."},"telefono":{"type":["string","null"],"description":"Phone for a NEW contact, when adding someone who is not in the rolodex yet. Null if they already exist or none was given."}},"required":["prestamo","contacto","rol","accion"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string"},"contacto":{"type":"string"},"rol":{"type":"string"},"accion":{"type":"string","enum":["add","remove"]},"orden":{"type":["number","null"]},"contacto_creado":{"type":"boolean"}},"required":["prestamo","contacto","rol","accion","orden","contacto_creado"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/assign_loan":{"post":{"operationId":"assign_loan","summary":"Assigns one or several loans or pre-quals to someone on the team — who owns the file and shows in the Assigned To column","description":"Assigns one or several loans or pre-quals to someone on the team — who owns the file and shows in the Assigned To column. Use it when the user hands files over: \"give the Garcia loan to Ana\", \"assign 412 Elm to me\", \"take Ana off the Miller file\" (pass asignado: null to leave it unassigned), \"put all of these under Ana\" (pass them in prestamos). With several files it does as many as it can and reports which ones failed, one by one. To attach a realtor, title company or any outside party use assign_loan_contact instead — that is about contacts, this is about your own team.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string","minLength":1,"description":"The file, as the user says it: borrower name, property address, or loan number. For several files use prestamos instead."},"prestamos":{"type":"array","items":{"type":"string","minLength":1},"description":"Several files at once, each one as the user says it. Use it when they hand over a batch (\"all of these go to Ana\")."},"asignado":{"type":["string","null"],"description":"Who takes it: a teammate name, their email, or \"me\". Null leaves the files unassigned."}},"required":["asignado"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":["number","null"]},"nombre":{"type":["string","null"]},"asignado_a":{"type":["string","null"]},"asignados":{"type":"number"},"resultados":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"nombre":{"type":"string"}},"required":["id","nombre"],"additionalProperties":false}},"fallidos":{"type":"array","items":{"type":"object","properties":{"prestamo":{"type":"string"},"error":{"type":"string"}},"required":["prestamo","error"],"additionalProperties":false}}},"required":["id","nombre","asignado_a","asignados","resultados","fallidos"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/update_conditions":{"post":{"operationId":"update_conditions","summary":"Manages the underwriting conditions of a loan: lists them, adds new ones, or marks one as received/approved/rejected/waived","description":"Manages the underwriting conditions of a loan: lists them, adds new ones, or marks one as received/approved/rejected/waived. Use it when the loan comes back approved with conditions (\"UW wants 2023 tax returns and a gift letter\"), when a condition clears (\"the gift letter came in\", \"VOE approved\"), or when asked what conditions are still open on a file. Marking one received stamps the day on its own, and if the condition asks for one of the six the office orders from third parties (title work, appraisal, HOA docs, HOI, payoff, LLC docs) it also shows up as received there — no need to mark it twice.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string","minLength":1,"description":"The loan, as the user says it: borrower name, address, or number"},"accion":{"type":"string","enum":["list","add","resolve"],"description":"'list' to see the conditions, 'add' to create new ones, 'resolve' to change the status of one"},"condiciones":{"anyOf":[{"type":"array","items":{"type":"string","minLength":1}},{"type":"null"}],"description":"For 'add': the text of each new condition. Null otherwise."},"condicion":{"type":["string","null"],"description":"For 'resolve': which condition, by a few words of its text. Null otherwise."},"asignado":{"type":["string","null"],"description":"Who has to get it — a name, or a role like 'the borrower', 'the processor'. With 'add' it applies to every condition in the batch; with 'resolve' it reassigns that one. Empty string clears it."},"estado":{"anyOf":[{"type":"string","enum":["pendiente","recibido","aprobado","rechazado","no_aplica"]},{"type":"null"}],"description":"For 'resolve': the new status. Defaults to 'aprobado' (cleared) when null."},"documento_origen":{"type":"string","maxLength":200,"description":"For 'add': the name of the approval document these conditions were read from (\"Approval_Miller.pdf\"), so the file history says where they came from. Pass it when they came out of read_approval_conditions."}},"required":["prestamo","accion","condiciones","condicion","estado"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string"},"condiciones":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"texto":{"type":"string"},"estado":{"type":"string"},"asignado":{"type":["string","null"]},"recibido_el":{"type":["string","null"]}},"required":["id","texto","estado","asignado","recibido_el"],"additionalProperties":false}},"pendientes":{"type":"number"}},"required":["prestamo","condiciones","pendientes"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/resend_status":{"post":{"operationId":"resend_status","summary":"Re-sends the status messages the file already earned, without moving it","description":"Re-sends the status messages the file already earned, without moving it. Use it when a borrower or realtor says they never got the update, or lost the link: \"resend the status to Maria\", \"send the link again\". It sends exactly what that stage sends — to write something different use send_message.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string","minLength":1,"description":"The file, as the user says it: borrower, address or number."}},"required":["prestamo"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"nombre":{"type":"string"},"estado":{"type":"string"},"encolados":{"type":"number"},"destinos":{"type":"array","items":{"type":"string"}}},"required":["nombre","estado","encolados","destinos"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_logs":{"post":{"operationId":"view_logs","summary":"Shows the change history of one loan, pre-qual or contact: what changed, when, and what triggered it","description":"Shows the change history of one loan, pre-qual or contact: what changed, when, and what triggered it. Use it when the user asks \"what happened with the Garcia file\", \"when did this move to underwriting\", \"history for Maria\", \"view logs\". Name the record the way the user does — borrower name, address, loan number, person name.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string","description":"The loan or pre-qual, as the user says it."},"contacto":{"type":"string","description":"The person, as the user says it."},"limite":{"type":"integer","minimum":1,"maximum":200,"description":"How many entries. Default 50."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"registro":{"type":"string"},"eventos":{"type":"array","items":{"type":"object","properties":{"cuando":{"type":"string"},"accion":{"type":"string"},"detalle":{"type":["string","null"]}},"required":["cuando","accion","detalle"],"additionalProperties":false}}},"required":["registro","eventos"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/delete_loan":{"post":{"operationId":"delete_loan","summary":"Permanently deletes a loan or pre-qualification and everything hanging off it: participants, documents, financial items and history","description":"Permanently deletes a loan or pre-qualification and everything hanging off it: participants, documents, financial items and history. Use it ONLY for a file created by mistake or a duplicate. A file that did not close is NOT deleted — move it to Cancelled or Archived with move_stage so its history survives. Ask the user to confirm before calling with confirmar: true.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string","minLength":1,"description":"The file, as the user says it: borrower, address or number."},"confirmar":{"type":"boolean","description":"Must be true to delete. Call it first with false to see what would be lost, show that to the user, and only then call with true."}},"required":["prestamo","confirmar"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"borrado":{"type":"boolean"},"nombre":{"type":"string"},"arrastra":{"type":"object","properties":{"participantes":{"type":"number"},"documentos":{"type":"number"},"items":{"type":"number"},"tareas":{"type":"number"}},"required":["participantes","documentos","items","tareas"],"additionalProperties":false}},"required":["borrado","nombre","arrastra"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_arive_loans":{"post":{"operationId":"view_arive_loans","summary":"Lists loans that live in Arive, the external loan origination system where the team builds the lender package","description":"Lists loans that live in Arive, the external loan origination system where the team builds the lender package. Use it when the user asks what is in Arive, wants to search Arive by borrower name, email, phone or loan number, or wants to compare Arive against the files tracked here. Read-only: nothing in Modellus can change Arive. For files tracked in Modellus use view_pipeline or find_loan instead. For the full detail of one Arive loan use view_arive_loan.","tags":["arive"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"search":{"type":"string","description":"Borrower name, email, phone, or Arive loan number to search for. Leave out to list the most recently updated loans."},"limit":{"type":"integer","minimum":1,"maximum":100,"description":"How many loans to return. Default 20."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"number"},"loans":{"type":"array","items":{"type":"object","properties":{"arive_loan_id":{"type":"number"},"borrowers":{"type":"array","items":{"type":"string"}},"status":{"type":["string","null"]},"purpose":{"type":["string","null"]},"mortgage_type":{"type":["string","null"]},"loan_amount":{"type":["number","null"]},"property":{"type":["string","null"]},"archived":{"type":"boolean"},"last_updated":{"type":["string","null"]},"arive_link":{"type":["string","null"]}},"required":["arive_loan_id","borrowers","status","purpose","mortgage_type","loan_amount","property","archived","last_updated","arive_link"],"additionalProperties":false}}},"required":["total","loans"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_arive_loan":{"post":{"operationId":"view_arive_loan","summary":"Shows the full file of ONE loan as it stands in Arive, the external loan origination system: status, amounts, rate and lock, key dates, borrowers, loan team and business contacts (title, escrow…), plus the direct link to open it in Arive","description":"Shows the full file of ONE loan as it stands in Arive, the external loan origination system: status, amounts, rate and lock, key dates, borrowers, loan team and business contacts (title, escrow…), plus the direct link to open it in Arive. Use it when the user asks how a specific loan looks in Arive, for a rate/lock/key-date that only Arive has, or to compare an Arive file against the one tracked here. Accepts a borrower name or an Arive loan number. Read-only: to change anything, the team edits it in Arive itself.","tags":["arive"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"loan":{"type":"string","minLength":1,"description":"The loan, as the user says it: a borrower name or an Arive loan number."}},"required":["loan"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"arive_loan_id":{"type":"number"},"arive_link":{"type":["string","null"]},"status":{"type":["string","null"]},"status_date":{"type":["string","null"]},"archived":{"type":"boolean"},"purpose":{"type":["string","null"]},"mortgage_type":{"type":["string","null"]},"property":{"type":["string","null"]},"amounts":{"type":"object","properties":{"base_loan_amount":{"type":["number","null"]},"total_loan_amount":{"type":["number","null"]},"purchase_price":{"type":["number","null"]},"down_payment":{"type":["number","null"]},"ltv":{"type":["number","null"]},"note_rate":{"type":["number","null"]},"loan_term":{"type":["number","null"]},"fico":{"type":["number","null"]}},"required":["base_loan_amount","total_loan_amount","purchase_price","down_payment","ltv","note_rate","loan_term","fico"],"additionalProperties":false},"lock":{"type":"object","properties":{"status":{"type":["string","null"]},"date":{"type":["string","null"]},"expiration":{"type":["string","null"]}},"required":["status","date","expiration"],"additionalProperties":false},"lender":{"type":"object","properties":{"name":{"type":["string","null"]},"loan_number":{"type":["string","null"]}},"required":["name","loan_number"],"additionalProperties":false},"borrowers":{"type":"array","items":{"type":"object","properties":{"nombre":{"type":"string"},"rol":{"type":["string","null"]},"email":{"type":["string","null"]},"telefono":{"type":["string","null"]}},"required":["nombre","rol","email","telefono"],"additionalProperties":false}},"loan_team":{"type":"array","items":{"type":"object","properties":{"nombre":{"type":"string"},"rol":{"type":["string","null"]},"email":{"type":["string","null"]},"telefono":{"type":["string","null"]}},"required":["nombre","rol","email","telefono"],"additionalProperties":false}},"business_contacts":{"type":"array","items":{"type":"object","properties":{"nombre":{"type":"string"},"rol":{"type":["string","null"]},"email":{"type":["string","null"]},"telefono":{"type":["string","null"]},"company":{"type":["string","null"]}},"required":["nombre","rol","email","telefono","company"],"additionalProperties":false}},"key_dates":{"type":"object","additionalProperties":{"type":"string"}},"created":{"type":["string","null"]},"last_updated":{"type":["string","null"]}},"required":["arive_loan_id","arive_link","status","status_date","archived","purpose","mortgage_type","property","amounts","lock","lender","borrowers","loan_team","business_contacts","key_dates","created","last_updated"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/connect_arive":{"post":{"operationId":"connect_arive","summary":"Connects this account to Arive, the external loan origination system, using API credentials from Arive (client id, secret key and API key)","description":"Connects this account to Arive, the external loan origination system, using API credentials from Arive (client id, secret key and API key). Use it when the user pastes their Arive API credentials, asks to connect or reconnect Arive, or when an Arive action failed because Arive is not connected yet. It validates the credentials live against Arive before saving, so wrong keys never get stored. Never repeat the credentials back in chat.","tags":["arive"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"client_id":{"type":"string","minLength":1,"description":"The client id of the Arive API client."},"secret_key":{"type":"string","minLength":1,"description":"The secret key of the Arive API client."},"api_key":{"type":"string","minLength":1,"description":"The API key of the Arive API client."}},"required":["client_id","secret_key","api_key"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"loans_visible":{"type":"number"}},"required":["ok","loans_visible"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/set_arive_cutoff":{"post":{"operationId":"set_arive_cutoff","summary":"Sets (or clears) the Arive cutoff date: loans CREATED in Arive before this date are never brought into Modellus, so a migration from the old system does not get duplicated by the sync","description":"Sets (or clears) the Arive cutoff date: loans CREATED in Arive before this date are never brought into Modellus, so a migration from the old system does not get duplicated by the sync. Loans already here keep being refreshed no matter how old they are. Use it when the user asks to only bring loans from a date onward, to change or remove that date, or when import_arive_loans reported older loans left out and they want them in from now on. Pass an empty string to remove the cutoff entirely (everything in Arive becomes importable). view_integrations shows the date currently in effect.","tags":["arive"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"created_after":{"type":"string","description":"The cutoff date as YYYY-MM-DD: only loans Arive created after it get imported. An empty string removes the cutoff."}},"required":["created_after"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"created_after":{"type":["string","null"]},"previous":{"type":["string","null"]}},"required":["created_after","previous"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/set_arive_live":{"post":{"operationId":"set_arive_live","summary":"Turns Arive live updates on or off","description":"Turns Arive live updates on or off. With them on, a change made in Arive shows up here within seconds instead of waiting for the sync that runs every 15 minutes (that sync keeps running either way, as a safety net). Use it when the user says Arive changes take too long to appear, asks for real-time or instant sync, or wants to stop the live updates. Pass action \"status\" to just report whether they are on.","tags":["arive"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["on","off","status"],"description":"Turn live updates on, off, or just report the current state."}},"required":["action"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"live":{"type":"boolean"},"webhook_url":{"type":["string","null"]},"events":{"type":"array","items":{"type":"string"},"maxItems":50},"failed":{"type":"array","items":{"type":"string"},"maxItems":50},"last_event_at":{"type":["string","null"]},"fallback_sync":{"type":"string"}},"required":["live","webhook_url","events","failed","last_event_at","fallback_sync"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/import_arive_loans":{"post":{"operationId":"import_arive_loans","summary":"Imports the active/pending loans from Arive into the Modellus pipeline: creates each loan with its borrowers (deduplicated against existing contacts), mapped stage, amounts, key dates and the link back to Arive","description":"Imports the active/pending loans from Arive into the Modellus pipeline: creates each loan with its borrowers (deduplicated against existing contacts), mapped stage, amounts, key dates and the link back to Arive. Closed, cancelled, funded and archived loans stay in Arive. Safe to run again: a loan already imported is refreshed (stage, data, dates), never duplicated. Use it when the user asks to bring, sync or import their Arive loans or pipeline into Modellus. ALWAYS run it first with preview=true and show the user what would be imported; only run with preview=false after they confirm. Requires Arive to be connected on this account (connect_arive). What it brings in for the first time lands in the New Client inbox waiting to be reviewed (view_new_clients / review_new_client), not straight into the pipeline. By default it only brings what Arive created AFTER the account cutoff date; older_than_cutoff in the result says how many were left out, so tell the user \"there are N older ones, want them too?\" and re-run with include_older=true if they say yes (set_arive_cutoff changes the date for good).","tags":["arive"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"preview":{"type":"boolean","description":"true = only count and list what would be imported, writing NOTHING. false = actually import. Always preview first."},"limit":{"type":"integer","minimum":1,"maximum":1000,"description":"Import at most this many loans (most recently updated first). Leave out to import all active/pending loans."},"include_older":{"type":"boolean","description":"By default only loans Arive created AFTER the account cutoff date come in — that is what keeps a migration from the old system from being duplicated. true also brings the older ones. Only when the user asks for them on purpose."}},"required":["preview"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"preview":{"type":"boolean"},"total_in_arive":{"type":"number"},"in_progress":{"type":"number"},"new_files":{"type":"number"},"already_tracked":{"type":"number"},"imported":{"type":"number"},"refreshed":{"type":"number"},"contacts_created":{"type":"number"},"failed":{"type":"number"},"new_clients_pending":{"type":"number"},"ignored_dropped":{"type":"number"},"older_than_cutoff":{"type":"number"},"sample":{"type":"array","items":{"type":"object","properties":{"arive_id":{"type":"number"},"borrower":{"type":"string"},"arive_status":{"type":"string"},"status":{"type":"string"},"amount":{"type":["number","null"]},"already_tracked":{"type":"boolean"}},"required":["arive_id","borrower","arive_status","status","amount","already_tracked"],"additionalProperties":false}}},"required":["preview","total_in_arive","in_progress","new_files","already_tracked","imported","refreshed","contacts_created","failed","new_clients_pending","ignored_dropped","older_than_cutoff","sample"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/resync_loan":{"post":{"operationId":"resync_loan","summary":"Pulls ONE loan from Arive right now and applies what changed, instead of waiting for the sync that runs every 15 minutes","description":"Pulls ONE loan from Arive right now and applies what changed, instead of waiting for the sync that runs every 15 minutes. Use it when the user says something was just changed on the Arive side and they want it here: \"refresh the Garcia loan from Arive\", \"the processor just updated 412 Elm, pull it\", \"this one looks out of date\". Hand-edited fields are kept unless Arive changed that same field. To bring in loans that are not in Modellus yet, use import_arive_loans instead.","tags":["pipeline"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string","minLength":1,"description":"The file, as the user says it: borrower name, property address, or loan number"}},"required":["prestamo"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"number"},"nombre":{"type":"string"},"cambios":{"type":"number"},"resumen":{"type":"string"}},"required":["id","nombre","cambios","resumen"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_new_clients":{"post":{"operationId":"view_new_clients","summary":"Lists the New Client inbox: loans that came in from Arive and nobody has reviewed yet — each one still has to be accepted as a loan, accepted as a pre-qualification, or ignored","description":"Lists the New Client inbox: loans that came in from Arive and nobody has reviewed yet — each one still has to be accepted as a loan, accepted as a pre-qualification, or ignored. Use it when the user asks what came in from Arive, what is waiting for review, what is in the New Client list, or how many new clients are pending. Pass `query` to look for one of them by borrower, email, phone, address or loan number. Each row brings the borrower with their email, phone and property, the Arive id and link, and how many files that person already has here. To act on one of them use review_new_client. For the files already being worked use view_pipeline instead.","tags":["arive"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"show":{"type":"string","enum":["pending","ignored"],"description":"'pending' (default) = waiting for review. 'ignored' = the Arive loans the team told Modellus to stop importing; use it when they ask what was ignored or want one back."},"query":{"type":"string","minLength":1,"maxLength":100,"description":"Narrows the inbox to the rows that mention this: borrower name, email, phone, property address or loan number. Use it when they ask something like \"did anything come in for the Millers\" or \"is that Oak Street file in the new client list\"."},"limit":{"type":"integer","minimum":1,"maximum":200,"description":"How many rows to return. Default 50."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"pending":{"type":"number"},"ignored":{"type":"number"},"clients":{"type":"array","items":{"type":"object","properties":{"loan_id":{"type":"number"},"borrower":{"type":"string"},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"address":{"type":["string","null"]},"arive_id":{"type":["number","null"]},"arive_link":{"type":["string","null"]},"loans_for_borrower":{"type":"number"},"amount":{"type":["number","null"]},"status":{"type":"string"},"imported_at":{"type":"string"}},"required":["loan_id","borrower","email","phone","address","arive_id","arive_link","loans_for_borrower","amount","status","imported_at"],"additionalProperties":false}},"ignored_arive_ids":{"type":"array","items":{"type":"number"}}},"required":["pending","ignored","clients","ignored_arive_ids"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/review_new_client":{"post":{"operationId":"review_new_client","summary":"Decides what happens with one loan sitting in the New Client inbox (the ones imported from Arive that nobody has reviewed)","description":"Decides what happens with one loan sitting in the New Client inbox (the ones imported from Arive that nobody has reviewed). Use it when the user says to accept, take, keep, convert or ignore something that came in from Arive: \"keep the Garcia one as a loan\", \"that one is just a pre-qual\", \"ignore the 412 Elm file\". decision=loan keeps it as an active loan, decision=prequal turns it into a pre-qualification, and decision=ignore DELETES it here and stops future imports from bringing it back — always confirm with the user before ignoring. decision=restore undoes an ignore (pass the Arive id) so the next import brings that loan again. List what is waiting with view_new_clients.","tags":["arive"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"loan":{"type":"string","minLength":1,"description":"Which one, as the user says it: borrower name, property address or Arive number. For decision=restore it must be the Arive id of the ignored loan (view_new_clients with show=ignored lists them)."},"decision":{"type":"string","enum":["loan","prequal","ignore","restore"],"description":"'loan' = accept it as an active loan. 'prequal' = accept it as a pre-qualification. 'ignore' = delete it here and never import it again. 'restore' = un-ignore an Arive id."}},"required":["loan","decision"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"loan_id":{"type":["number","null"]},"name":{"type":"string"},"decision":{"type":"string"},"status":{"type":["string","null"]},"pending":{"type":"number"}},"required":["loan_id","name","decision","status","pending"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/check_documents":{"post":{"operationId":"check_documents","summary":"Shows the document checklist","description":"Shows the document checklist. With a loan (a phrase like \"the Martinez loan\" or an address), it lists every document grouped by status and by who owes it. Without a loan, it gives a cross-pipeline report of every loan with pending documents and how many days they have been waiting. Use it whenever the user asks what is missing, what came in, or what is stuck.","tags":["documents"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"loan":{"type":"string","description":"The loan, as the user says it (\"the Martinez loan\", \"123 Main St\"). Leave empty for the cross-pipeline report."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"resumen":{"type":"string"},"prestamos":{"type":"array","items":{"type":"object","properties":{"prestamo_id":{"type":"number"},"prestamo":{"type":"string"},"pendientes":{"type":"number"},"recibidos":{"type":"number"},"aprobados":{"type":"number"},"rechazados":{"type":"number"},"en_seguimiento":{"type":"number"},"incompletos":{"type":"number"},"dias_esperando":{"type":["number","null"]},"por_parte":{"type":"array","items":{"type":"object","properties":{"parte":{"type":"string"},"documentos":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"tipo":{"type":"string"},"documento":{"type":"string"},"estado":{"type":"string"},"motivo_rechazo":{"type":["string","null"]},"periodo":{"type":["string","null"]},"archivo":{"type":"boolean"},"versiones":{"type":"number"},"regla":{"type":["string","null"]},"es_condicion":{"type":"boolean"},"participante_id":{"type":["number","null"]}},"required":["id","tipo","documento","estado","motivo_rechazo","periodo","archivo","versiones","regla","es_condicion","participante_id"],"additionalProperties":false}}},"required":["parte","documentos"],"additionalProperties":false}}},"required":["prestamo_id","prestamo","pendientes","recibidos","aprobados","rechazados","en_seguimiento","incompletos","dias_esperando","por_parte"],"additionalProperties":false}}},"required":["resumen","prestamos"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/request_documents":{"post":{"operationId":"request_documents","summary":"Prepares the document request for a loan: refreshes the checklist, groups what is missing by who owes it (borrower, realtor, title company, HOA), creates a secure upload portal link for each party, and queues a reminder message (email and text, whichever the contact has) with the link and the list of missing documents","description":"Prepares the document request for a loan: refreshes the checklist, groups what is missing by who owes it (borrower, realtor, title company, HOA), creates a secure upload portal link for each party, and queues a reminder message (email and text, whichever the contact has) with the link and the list of missing documents. Use it when the user wants to chase, collect or request the missing documents of a loan. The messages go out within a minute; the response says exactly what was queued to whom.","tags":["documents"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"loan":{"type":"string","minLength":1,"description":"The loan, as the user says it (\"the Martinez loan\", \"123 Main St\")."},"solo":{"type":"array","items":{"type":"integer","exclusiveMinimum":0},"description":"Document ids to ask for, when only some of what is missing goes out. Omit to ask for everything that is missing."},"modo":{"type":"string","enum":["request","reminder"],"description":"'request' is the first ask; 'reminder' is the nudge for what was already asked. Default is request."},"confirmar":{"type":"boolean","description":"false (or omitted) previews what WOULD go out to whom, without sending anything. true sends it. Show the preview first."}},"required":["loan"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"aviso":{"type":"string"},"enviado":{"type":"boolean"},"partes":{"type":"array","items":{"type":"object","properties":{"parte":{"type":"string"},"rol":{"type":["string","null"]},"docs_faltantes":{"type":"array","items":{"type":"string"}},"datos_faltantes":{"type":"array","items":{"type":"string"}},"enlace":{"type":["string","null"]},"mensajes_encolados":{"type":"array","items":{"type":"string"}}},"required":["parte","rol","docs_faltantes","datos_faltantes","enlace","mensajes_encolados"],"additionalProperties":false}}},"required":["aviso","enviado","partes"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/review_document":{"post":{"operationId":"review_document","summary":"Closes out a document that came in: approve it, reject it, or flag it as incomplete","description":"Closes out a document that came in: approve it, reject it, or flag it as incomplete. Identify it by document id, or by loan plus document type (\"reject the bank statement on the Martinez loan\"). Use 'missing_information' when what arrived is right but partial (only page 1 of 4 came through) — it asks for the missing piece instead of the whole document again; use 'reject' when it is the wrong or unusable document. Both need a reason: it is what the borrower reads to fix it. Approving takes optional notes for the file. Use it when the user reviews paperwork from the chat instead of the review queue.","tags":["documents"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"document_id":{"type":"integer","description":"The document id, when it is already known (e.g. from check_documents)."},"loan":{"type":"string","description":"The loan, as the user says it. Needed when there is no document_id."},"document_type":{"type":"string","description":"The document type, e.g. \"w2\", \"bank_statement_personal\", \"paystub\"."},"decision":{"type":"string","enum":["approve","reject","missing_information"],"description":"Accepted, sent back to be redone, or received but incomplete."},"reason":{"type":"string","description":"Why it is sent back, or exactly what is missing — written for the person who has to fix it. Required for both reject and missing_information."},"notes":{"type":"string","maxLength":500,"description":"Only when approving: what to leave written on the file about this document (\"accepted without the stamp, spoke to the bank\")."}},"required":["decision"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"documento":{"type":"string"},"estado":{"type":"string"},"motivo":{"type":["string","null"]},"notas":{"type":["string","null"]}},"required":["documento","estado","motivo","notas"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/update_document":{"post":{"operationId":"update_document","summary":"Sets what is expected of one document on a loan: the period it has to cover (\"bank statements from January to March\", \"the 2024 tax return\"), a note for whoever provides it, and where it stands: still pending, already requested and waiting on them (follow_up), already received by other means, or not applicable to this file","description":"Sets what is expected of one document on a loan: the period it has to cover (\"bank statements from January to March\", \"the 2024 tax return\"), a note for whoever provides it, and where it stands: still pending, already requested and waiting on them (follow_up), already received by other means, or not applicable to this file. It is ALSO how you change WHO the document is asked from (\"ask the realtor for the HOA questionnaire on this one\", \"the insurance binder goes to the insurance agent, not the borrower\") — there is no rule per document type, the loan officer decides case by case, and the request messages follow whoever is set here. Use it when the user says \"the statements should be Jan through Mar\", \"I already have the W-2, mark it in\", \"I just chased him for the paystub\" (follow_up), \"we do not need the survey here\", or \"ask Maria for that one instead\". If the document is not on the loan’s checklist yet it gets added, so you can set a status on any document type without a second step. Every status change stamps its own date — you only pass status_date when the user names a different day. To approve or reject a file that came in, use review_document. For the six the office orders from third parties (title work, appraisal, HOA docs, HOI, payoff, LLC docs) order_docs says it in one word and covers the whole package.","tags":["documents"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"document_id":{"type":"integer","description":"The document id, when known (e.g. from check_documents)."},"loan":{"type":"string","description":"The loan, as the user says it. Needed when there is no document_id."},"document_type":{"type":"string","description":"The document type, e.g. \"bank_statement_personal\", \"w2\"."},"period_from":{"type":"string","description":"First day the document has to cover (ISO date). Empty string clears it."},"period_to":{"type":"string","description":"Last day the document has to cover (ISO date). Empty string clears it."},"note":{"type":"string","maxLength":500,"description":"What to tell whoever provides it. Empty string clears it."},"request_from":{"type":"string","description":"Who this document is asked from, as the user says it: a name (\"Maria\", \"Robert\") or a role on this file (\"the realtor\", \"title company\", \"the HOA\"). They have to already be a participant on the loan. An empty string puts it back on the borrower. The same document goes to different people on different files — never assume from the document type."},"status":{"type":"string","enum":["pending","follow_up","received","not_applicable"],"description":"'follow_up' means it was already requested and you are waiting on them — use it after chasing someone; 'received' is for paperwork that arrived outside the portal; 'not_applicable' takes it off the list without erasing its history."},"status_date":{"type":"string","description":"The day the status change happened (ISO date), when it was NOT today: \"I ordered the appraisal on Monday\", \"the payoff came in on the 3rd\". Leave it out and the date is stamped on its own — never ask the user for it."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"documento":{"type":"string"},"estado":{"type":"string"},"periodo":{"type":["string","null"]},"pedido_a":{"type":"string"},"pedido_el":{"type":["string","null"]},"recibido_el":{"type":["string","null"]}},"required":["documento","estado","periodo","pedido_a","pedido_el","recibido_el"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/order_docs":{"post":{"operationId":"order_docs","summary":"The \"Docs Ordered\" board of a loan: the paperwork the office orders from third parties — Title Work, Appraisal, HOA Docs, HOI, Payoff, LLC Docs","description":"The \"Docs Ordered\" board of a loan: the paperwork the office orders from third parties — Title Work, Appraisal, HOA Docs, HOI, Payoff, LLC Docs. Unlike the rest of the checklist, nobody chases these from the borrower: they are ordered from title, the appraiser, the HOA or the insurance agent, and what matters is WHAT DAY they went out, so someone can call when they take too long. Use it when the user says \"I ordered the appraisal\", \"title work is out\", \"HOA docs came back today\", \"payoff received\", \"did we order HOI on the Miller file?\", or \"what is still not ordered\". Ordering stamps the date on its own — only pass status_date when they name a different day (\"I ordered it Monday\"). Leave `document` out to see the whole board. HOA Docs and LLC Docs are packages: one word covers every paper in them. For any other document type use update_document, and for underwriting conditions use update_conditions — a condition that asks for one of these six already shows up here as received on its own.","tags":["documents"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"loan":{"type":"string","minLength":1,"description":"The loan, as the user says it: borrower name, address, or number."},"document":{"type":"string","description":"Which one, as the user says it: \"title work\", \"appraisal\", \"HOA docs\", \"HOI\", \"payoff\", \"LLC docs\". Leave it out to just see the board."},"status":{"type":"string","enum":["ordered","received","not_ordered"],"description":"'ordered' means it went out and we are waiting; 'received' means it came in; 'not_ordered' undoes both — use it when they say they had not actually ordered it. Leave it out to only read."},"status_date":{"type":"string","description":"The day it happened (ISO date), when it was NOT today: \"I ordered it Monday\", \"the payoff came in on the 3rd\". Leave it out and the date is stamped on its own — never ask the user for it."}},"required":["loan"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string"},"documentos":{"type":"array","items":{"type":"object","properties":{"documento":{"type":"string"},"estado":{"type":"string","enum":["not_ordered","ordered","received"]},"pedido_el":{"type":["string","null"]},"recibido_el":{"type":["string","null"]},"desde_condicion":{"type":["string","null"]}},"required":["documento","estado","pedido_el","recibido_el","desde_condicion"],"additionalProperties":false}},"sin_pedir":{"type":"number"}},"required":["prestamo","documentos","sin_pedir"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/attach_document":{"post":{"operationId":"attach_document","summary":"Files a document the user attached in the chat into a loan's document checklist","description":"Files a document the user attached in the chat into a loan's document checklist. Use it when the user uploads a file and says what it is: \"this is Robert's W2\", \"here's the signed contract for the Garcia loan\". The file_path comes from the \"[Attached document: … — path: …]\" note in the conversation — never invent it, and never ask the user for it. Identify the checklist entry by loan plus document type (or by document id from check_documents). The document is marked as received, ready for review.","tags":["documents"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"file_path":{"type":"string","minLength":1,"description":"The storage path from the \"[Attached document: … — path: …]\" note of the attached file in this conversation."},"file_name":{"type":"string","description":"The original file name, from the same note."},"document_id":{"type":"integer","description":"The checklist document id, when it is already known (e.g. from check_documents)."},"loan":{"type":"string","description":"The loan, as the user says it: borrower name, address, or number. Needed when there is no document_id."},"document_type":{"type":"string","description":"The document type, e.g. \"w2\", \"bank_statement_personal\", \"paystub\". Needed when there is no document_id."}},"required":["file_path"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"documento":{"type":"string"},"prestamo":{"type":"string"},"estado":{"type":"string"},"archivo":{"type":"string"},"version":{"type":"number"}},"required":["documento","prestamo","estado","archivo","version"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/document_versions":{"post":{"operationId":"document_versions","summary":"Lists every version of one checklist document: each time it was sent in, who sent it, when, how it was closed out (approved, rejected, incomplete) and a link to open that exact file","description":"Lists every version of one checklist document: each time it was sent in, who sent it, when, how it was closed out (approved, rejected, incomplete) and a link to open that exact file. Use it when the user asks about a resubmission — \"what did he send the first time?\", \"show me the previous bank statement\", \"how many times have we asked for this?\" Identify the document by id (from check_documents) or by loan plus document type.","tags":["documents"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"document_id":{"type":"integer","description":"The document id, when known (e.g. from check_documents)."},"loan":{"type":"string","description":"The loan, as the user says it. Needed when there is no document_id."},"document_type":{"type":"string","description":"The document type, e.g. \"bank_statement_personal\", \"w2\"."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"documento":{"type":"string"},"resumen":{"type":"string"},"versiones":{"type":"array","items":{"type":"object","properties":{"version":{"type":"number"},"estado":{"type":"string"},"archivo":{"type":"string"},"subido_por":{"type":["string","null"]},"cuando":{"type":"string"},"motivo":{"type":["string","null"]},"notas":{"type":["string","null"]},"url":{"type":["string","null"]}},"required":["version","estado","archivo","subido_por","cuando","motivo","notas","url"],"additionalProperties":false}}},"required":["documento","resumen","versiones"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/send_portal_link":{"post":{"operationId":"send_portal_link","summary":"Creates (or renews) the secure upload portal link for one contact on one loan and returns the URL","description":"Creates (or renews) the secure upload portal link for one contact on one loan and returns the URL. Use it when the user asks for the link of a specific person — \"resend the portal to Maria\", \"give me the upload link for the title company\". Previous links for that person keep working until they expire (30 days).","tags":["documents"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"loan":{"type":"string","minLength":1,"description":"The loan, as the user says it (\"the Martinez loan\", \"123 Main St\")."},"contact":{"type":"string","minLength":1,"description":"The person, as the user says it (\"Maria\", \"the realtor of...\")."}},"required":["loan","contact"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"contacto":{"type":"string"},"enlace":{"type":"string"},"expira_el":{"type":"string"},"aviso":{"type":"string"}},"required":["contacto","enlace","expira_el","aviso"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/adjust_checklist":{"post":{"operationId":"adjust_checklist","summary":"Adjusts the checklist of ONE loan by hand: adds documents the rules did not derive (\"also ask for a divorce decree\") or marks documents as not applicable (\"we do not need the survey on this one\")","description":"Adjusts the checklist of ONE loan by hand: adds documents the rules did not derive (\"also ask for a divorce decree\") or marks documents as not applicable (\"we do not need the survey on this one\"). This is how you ask ONE borrower for their personal paperwork — their ID (driver's license, passport, social security card), their immigration documents (green card, visa, work permit, asylum documents), the VA ones (certificate of eligibility, DD-214) — and how you ask for a letter of explanation, which can be asked for several times on the same file, one per subject. For changing the office-wide rules use edit_checklist_rules instead.","tags":["documents"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"loan":{"type":"string","minLength":1,"description":"The loan, as the user says it (\"the Martinez loan\", \"123 Main St\")."},"add":{"type":"array","items":{"type":"object","properties":{"document_type":{"type":"string","description":"The document type, e.g. \"divorce_decree\", \"credit_explanation_letter\"."},"from":{"type":"string","description":"Who provides it, as the user says it (\"Maria\", \"the realtor\"). Defaults to the borrower."},"note":{"type":"string","description":"Why it is being asked (e.g. the underwriting condition text)."},"subject":{"type":"string","maxLength":200,"description":"What this one is about, when the same document type is asked for more than once on the same file — above all the letter of explanation: \"the late payments in 2023\", \"the gap in employment\". Each subject is its own request, and it is what the borrower reads, so write it as you would say it to them."}},"required":["document_type"],"additionalProperties":false},"description":"Documents to add to this loan."},"remove":{"type":"array","items":{"type":"string"},"description":"Document types to mark as not applicable on this loan."}},"required":["loan"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"agregados":{"type":"array","items":{"type":"string"},"maxItems":200},"retirados":{"type":"array","items":{"type":"string"},"maxItems":200}},"required":["agregados","retirados"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_checklist_rules":{"post":{"operationId":"view_checklist_rules","summary":"Explains the active document checklist rules of this office in plain English, including any per-account overrides (disabled types, how many bank statements or tax return years)","description":"Explains the active document checklist rules of this office in plain English, including any per-account overrides (disabled types, how many bank statements or tax return years). With a loan, it also explains which rule generated each document on that checklist. Use it when the user asks why a document is being requested or how the checklist works.","tags":["documents"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"loan":{"type":"string","description":"A loan, to also explain what generated each of its documents."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"reglas":{"type":"array","items":{"type":"string"}},"overrides":{"type":"object","additionalProperties":{}},"documentos_del_prestamo":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"documento":{"type":"string"},"origen":{"type":"string"},"razon":{"type":"string"}},"required":["documento","origen","razon"],"additionalProperties":false}},{"type":"null"}]}},"required":["reglas","overrides","documentos_del_prestamo"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/edit_checklist_rules":{"post":{"operationId":"edit_checklist_rules","summary":"Changes the office-wide checklist rules: disable a document type so it is never requested (\"we never ask for surveys\"), re-enable it, or change quantities (\"ask for 3 bank statements\", \"1 year of tax returns\")","description":"Changes the office-wide checklist rules: disable a document type so it is never requested (\"we never ask for surveys\"), re-enable it, or change quantities (\"ask for 3 bank statements\", \"1 year of tax returns\"). Applies to every loan the next time its checklist syncs. For one specific loan use adjust_checklist instead.","tags":["documents"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"disable":{"type":"array","items":{"type":"string"},"description":"Document types to stop requesting everywhere, e.g. [\"survey\"]."},"enable":{"type":"array","items":{"type":"string"},"description":"Previously disabled document types to request again."},"bank_statements":{"type":"integer","minimum":1,"maximum":12,"description":"How many bank statements to request per account (default 2)."},"tax_return_years":{"type":"integer","minimum":1,"maximum":5,"description":"How many years of tax returns to request (default 2)."},"w2_years":{"type":"integer","minimum":1,"maximum":5,"description":"How many years of W-2s to request (default 2)."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"overrides":{"type":"object","additionalProperties":{}},"resumen":{"type":"string"}},"required":["overrides","resumen"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/read_approval_conditions":{"post":{"operationId":"read_approval_conditions","summary":"Reads a lender approval document — the PDF the bank sends when a loan is approved with conditions, or a photo or screenshot of it — and pulls out the underwriting conditions as a clean list: bank statements, mortgage statements, rental income, liability verifications, letters of explanation","description":"Reads a lender approval document — the PDF the bank sends when a loan is approved with conditions, or a photo or screenshot of it — and pulls out the underwriting conditions as a clean list: bank statements, mortgage statements, rental income, liability verifications, letters of explanation. Use it whenever the user attaches an approval, a conditional approval or a commitment letter (\"here is the approval\", \"pull the conditions off this\", \"the bank sent this back\"). The file_path comes from the \"[Attached document: … — path: …]\" note in the conversation — never invent it. This only READS: nothing is saved. Show the user the list, let them drop the ones that do not belong, and only then call update_conditions with accion 'add' to put them on the file.","tags":["documents"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"file_path":{"type":"string","minLength":1,"description":"The storage path of the approval document, from the \"[Attached document: … — path: …]\" note of the attached file."},"file_name":{"type":"string","description":"The original file name, from the same note."},"loan":{"type":"string","description":"The loan the approval belongs to, as the user says it. Optional, but with it the answer says which conditions are already on the file so they do not get added twice."}},"required":["file_path"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"archivo":{"type":"string"},"prestamo":{"type":["string","null"]},"prestatario":{"type":["string","null"]},"es_aprobacion":{"type":"boolean"},"resumen":{"type":"string"},"condiciones":{"type":"array","items":{"type":"object","properties":{"texto":{"type":"string"},"asignado":{"type":["string","null"]},"categoria":{"type":["string","null"]},"ya_esta":{"type":"boolean"}},"required":["texto","asignado","categoria","ya_esta"],"additionalProperties":false}}},"required":["archivo","prestamo","prestatario","es_aprobacion","resumen","condiciones"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/send_message":{"post":{"operationId":"send_message","summary":"Sends an email, text (SMS) or WhatsApp message to a contact, written free-form or from a template","description":"Sends an email, text (SMS) or WhatsApp message to a contact, written free-form or from a template. IMPORTANT: it is a two-step action — call it first WITHOUT confirm to get the rendered draft, show that draft to the user and ask for their approval, and only when they approve call it again with confirm=true to actually send. Use it when the user wants to message, email or text someone: \"text Maria that we are waiting on her W-2\", \"send the Garcias the welcome email\". For reading past messages use view_conversation.","tags":["communication"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contacto":{"type":"string","minLength":1,"description":"Who, as the user says it: a name, email or phone number."},"canal":{"type":"string","enum":["email","sms","whatsapp"],"description":"How to send it. If omitted: email when the contact has one, otherwise sms."},"mensaje":{"type":"string","description":"Free-form body. Either this or plantilla is required."},"asunto":{"type":"string","description":"Email subject (free-form emails only)."},"plantilla":{"type":"string","description":"A template key ('docs_missing_reminder', 'weekly_summary'…) instead of free text. view_templates lists them."},"variables":{"type":"object","additionalProperties":{"type":"string"},"description":"Values for the {{variables}} of the template, when using one."},"confirm":{"type":"boolean","description":"true = the user already saw and approved the draft: send it. Never set it on the first call."}},"required":["contacto"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"enviado":{"type":"boolean"},"contacto":{"type":"string"},"canal":{"type":"string"},"destinatario":{"type":"string"},"borrador":{"type":"object","properties":{"asunto":{"type":["string","null"]},"cuerpo":{"type":"string"}},"required":["asunto","cuerpo"],"additionalProperties":false},"aviso":{"type":"string"}},"required":["enviado","contacto","canal","destinatario","borrador","aviso"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_conversation":{"post":{"operationId":"view_conversation","summary":"Shows the message history (emails, texts, WhatsApp — sent and received) with a contact, or all the messages around a loan","description":"Shows the message history (emails, texts, WhatsApp — sent and received) with a contact, or all the messages around a loan. Use it when the user asks \"what have we sent Maria?\", \"did the reminder go out?\", \"show me the thread on the Garcia file\". Give it a contact or a loan (at least one).","tags":["communication"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contacto":{"type":"string","description":"The person, as the user says it: name, email or phone."},"loan":{"type":"string","description":"The loan, as the user says it (\"the Martinez loan\", \"412 Elm\")."},"limite":{"type":"integer","minimum":1,"maximum":50,"description":"How many messages (default 20, newest first)."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"hilo":{"type":"string"},"mensajes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"canal":{"type":"string"},"direccion":{"type":"string"},"estado":{"type":"string"},"destinatario":{"type":"string"},"asunto":{"type":["string","null"]},"texto":{"type":"string"},"cuando":{"type":"string"}},"required":["id","canal","direccion","estado","destinatario","asunto","texto","cuando"],"additionalProperties":false}}},"required":["hilo","mensajes"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_templates":{"post":{"operationId":"view_templates","summary":"Lists the message templates: their key, what each one says by default, whether the account customized them, and which pipeline transition triggers each one automatically","description":"Lists the message templates: their key, what each one says by default, whether the account customized them, and which pipeline transition triggers each one automatically. Every template exists in English and Spanish, for email and for text, and asking for one key returns all four bodies — each contact receives the language their profile says (set_contact_language changes it). Use it when the user asks what messages go out, wants to see a template before editing it, or before send_message with a template key. To change one, use edit_template.","tags":["communication"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"clave":{"type":"string","description":"One template key to see in full (with its default bodies). Omit to list them all."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"plantillas":{"type":"array","items":{"type":"object","properties":{"clave":{"type":"string"},"label":{"type":"string"},"variables":{"type":"array","items":{"type":"string"}},"idiomas":{"type":"array","items":{"type":"string"}},"disparadores":{"type":"array","items":{"type":"string"}},"personalizaciones":{"type":"array","items":{"type":"string"}},"cuerpos":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"canal":{"type":"string"},"idioma":{"type":"string"},"asunto":{"type":["string","null"]},"cuerpo":{"type":"string"},"fuente":{"type":"string","enum":["published","draft","default"]},"bloques":{"anyOf":[{"type":"array","items":{"type":"object","additionalProperties":{}}},{"type":"null"}]}},"required":["canal","idioma","asunto","cuerpo","fuente","bloques"],"additionalProperties":false}},{"type":"null"}]}},"required":["clave","label","variables","idiomas","disparadores","personalizaciones","cuerpos"],"additionalProperties":false}}},"required":["plantillas"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/edit_template":{"post":{"operationId":"edit_template","summary":"Edits a message template for this account: saves a draft (default) or publishes it (publish=true) so it replaces the stock wording in every future send","description":"Edits a message template for this account: saves a draft (default) or publishes it (publish=true) so it replaces the stock wording in every future send. Use it when the user wants to change what a message says: \"make the docs reminder friendlier\", \"add my calendly to the welcome email\". Variables go in double braces — {{borrower_name}}, {{portal_link}}… — view_templates lists each template's variables. Every template exists in English and Spanish: pass idioma='es' to change the Spanish wording, which is what Spanish-speaking borrowers receive. It always returns a test render with sample data: show it to the user.","tags":["communication"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"clave":{"type":"string","minLength":1,"description":"The template key ('docs_missing_reminder'…)."},"canal":{"type":"string","enum":["email","sms","whatsapp"],"description":"Which channel version to edit."},"idioma":{"type":"string","enum":["en","es"],"description":"Which language version: 'en' (default) or 'es'. Every template exists in both, and each contact gets the one their preferred language says — editing one language does not touch the other."},"cuerpo":{"type":"string","description":"The new body, with {{variables}} where data should go. Leave it empty (\"\") only when you are sending `bloques` instead."},"bloques":{"anyOf":[{"type":"array","items":{"type":"object","properties":{"tipo":{"type":"string","enum":["titulo","parrafo","lista","boton","separador","firma"],"description":"What kind of block this is."},"texto":{"type":["string","null"],"description":"The text of a heading, a paragraph or a button label."},"items":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"The lines of a list block."},"url":{"type":["string","null"],"description":"Where a button block links to."}},"required":["tipo","texto","items","url"],"additionalProperties":false}},{"type":"null"}],"description":"The formatted email, block by block, for the visual editor. Only for email: it produces both the styled HTML and the plain text (which is what goes in `cuerpo`, so leave `cuerpo` empty when you send blocks). Null for a plain-text template."},"asunto":{"type":"string","description":"The new subject (email only)."},"publish":{"type":"boolean","description":"true = make it live for every future send. Default: save as draft only."},"restablecer":{"type":["boolean","null"],"description":"true throws away the customization and goes back to the stock wording (the \"reset to default\" of 1.0). When true, `cuerpo` and `bloques` are ignored. Null or false to edit normally."}},"required":["clave","canal","cuerpo","bloques","restablecer"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"clave":{"type":"string"},"canal":{"type":"string"},"idioma":{"type":"string"},"estado":{"type":"string"},"render_de_prueba":{"type":"object","properties":{"asunto":{"type":["string","null"]},"cuerpo":{"type":"string"}},"required":["asunto","cuerpo"],"additionalProperties":false},"aviso":{"type":"string"}},"required":["clave","canal","idioma","estado","render_de_prueba","aviso"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/generate_preapproval_letter":{"post":{"operationId":"generate_preapproval_letter","summary":"Generates the official pre-approval letter (PDF) for a loan: amount, program, expiration and the standard disclaimers, signed by the loan officer","description":"Generates the official pre-approval letter (PDF) for a loan: amount, program, expiration and the standard disclaimers, signed by the loan officer. It uploads the PDF to the file's documents and returns a download link that lasts 7 days. Use it when the user asks for a pre-approval letter: \"generate the pre-approval for Garcia\", \"make me a letter for 320k for the Elm St file\". Amount and expiration are optional: without them it uses the loan amount on file and 60 days.","tags":["communication"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"loan":{"type":"string","minLength":1,"description":"The loan, as the user says it (\"the Garcia file\", \"412 Elm\")."},"monto":{"type":"number","exclusiveMinimum":0,"description":"Approved amount in USD. Omit to use the loan amount on file."},"expira":{"type":"string","description":"Expiration date (YYYY-MM-DD). Omit for 60 days from today."},"tasa":{"type":"number","exclusiveMinimum":0,"description":"Interest rate to show, if the user wants it on the letter."}},"required":["loan"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"prestamo":{"type":"string"},"borrower":{"type":"string"},"monto":{"type":"number"},"vence":{"type":"string"},"enlace":{"type":"string"},"aviso":{"type":"string"}},"required":["prestamo","borrower","monto","vence","enlace","aviso"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_automations":{"post":{"operationId":"view_automations","summary":"Shows every automation of the account: which are on or off, their cadence in days, when the scheduled jobs run, and the two master switches (email and text messages) that sit above all of them","description":"Shows every automation of the account: which are on or off, their cadence in days, when the scheduled jobs run, and the two master switches (email and text messages) that sit above all of them. Use it when the user asks \"what runs automatically\", \"is the document reminder on\", \"how often do borrowers get reminded\", \"why is nothing going out\", or before changing one. To turn one automation on/off or change its cadence use set_automation; to turn a whole channel on or off use set_channel.","tags":["communication"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"canales":{"type":"object","properties":{"email":{"type":"boolean"},"sms":{"type":"boolean"}},"required":["email","sms"],"additionalProperties":false,"description":"The account master switches. With one off, nothing goes out through it no matter how the automations are set."},"automatizaciones":{"type":"array","items":{"type":"object","properties":{"clave":{"type":"string"},"nombre":{"type":"string"},"activa":{"type":"boolean"},"cadencia_dias":{"type":["number","null"]}},"required":["clave","nombre","activa","cadencia_dias"],"additionalProperties":false}},"programacion":{"type":"array","items":{"type":"object","properties":{"trabajo":{"type":"string"},"cuando":{"type":"string"}},"required":["trabajo","cuando"],"additionalProperties":false}}},"required":["canales","automatizaciones","programacion"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/set_automation":{"post":{"operationId":"set_automation","summary":"Turns an automation on or off, or changes its cadence in days","description":"Turns an automation on or off, or changes its cadence in days. Use it when the user says \"stop reminding borrowers\", \"turn the document reminder back on\", \"remind every 5 days instead\", or any change to what runs automatically. The automation is whatever the user calls it (\"the document reminder\"); view_automations lists the exact keys.","tags":["communication"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"automation":{"type":"string","minLength":1,"description":"Which automation, as the user says it: the key (\"docs_missing_reminder\") or words from its name (\"document reminder\")."},"enabled":{"type":"boolean","description":"true to turn it on, false to turn it off."},"cadence_days":{"type":"integer","minimum":1,"maximum":30,"description":"Every how many days it fires (for automations with a cadence)."}},"required":["automation"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"clave":{"type":"string"},"activa":{"type":"boolean"},"cadencia_dias":{"type":["number","null"]},"resumen":{"type":"string"}},"required":["clave","activa","cadencia_dias","resumen"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/set_channel":{"post":{"operationId":"set_channel","summary":"Turns a whole messaging channel on or off for the entire account: email, text messages (SMS and WhatsApp), or both at once","description":"Turns a whole messaging channel on or off for the entire account: email, text messages (SMS and WhatsApp), or both at once. Use it when the user wants everything to stop or restart, not one automation and not one person: \"stop all texts\", \"pause every email while I am out\", \"turn messaging back on\". While a channel is off, nothing goes out through it — automations included — and whatever the app tries to send is held as blocked so it can be retried later with retry_message. To see how the switches are right now use view_automations; to stop just one automation use set_automation; to stop messaging one person use set_communication_preferences.","tags":["communication"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","enum":["email","sms","all"],"description":"'email' for every email, 'sms' for every text message (SMS and WhatsApp), 'all' for both."},"enabled":{"type":"boolean","description":"true turns the channel back on, false turns it off."}},"required":["channel","enabled"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"boolean"},"sms":{"type":"boolean"},"resumen":{"type":"string"}},"required":["email","sms","resumen"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_delivery":{"post":{"operationId":"view_delivery","summary":"Shows the outgoing messages that did not make it: the ones the provider rejected after every retry, and the ones held back because the account had that channel turned off","description":"Shows the outgoing messages that did not make it: the ones the provider rejected after every retry, and the ones held back because the account had that channel turned off. Also reports how many are waiting in the queue and how the master switches are. Use it when the user asks \"did that email go out\", \"why has Maria not heard from me\", \"is anything stuck\", \"what failed\", or when a message they expected never arrived. To send one again use retry_message; to turn a channel back on use set_channel.","tags":["communication"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limite":{"type":"integer","minimum":1,"maximum":50,"description":"How many to list, newest first. Default 20."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"canales":{"type":"object","properties":{"email":{"type":"boolean"},"sms":{"type":"boolean"}},"required":["email","sms"],"additionalProperties":false},"en_cola":{"type":"number","description":"Messages waiting to go out right now."},"atascados":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number","description":"Pass this to retry_message."},"canal":{"type":"string"},"contacto":{"type":["string","null"]},"destinatario":{"type":"string"},"motivo":{"type":"string","description":"'failed' (the provider rejected it) or 'channel_off'."},"error":{"type":"string"},"intentos":{"type":"number"},"vistazo":{"type":"string","description":"Subject, or the start of the text."},"creado":{"type":"string"}},"required":["id","canal","contacto","destinatario","motivo","error","intentos","vistazo","creado"],"additionalProperties":false}},"resumen":{"type":"string"}},"required":["canales","en_cola","atascados","resumen"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/retry_message":{"post":{"operationId":"retry_message","summary":"Sends a failed or blocked outgoing message again: it goes back into the queue and leaves within a minute","description":"Sends a failed or blocked outgoing message again: it goes back into the queue and leaves within a minute. Use it after view_delivery shows something stuck and the user says \"try again\", \"resend that\", \"send them all again\". Pass the message id from view_delivery, or all=true to retry every stuck message at once. A message cancelled because the person opted out is never retried.","tags":["communication"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"message_id":{"type":"integer","description":"The id view_delivery gave for that message."},"all":{"type":"boolean","description":"true = retry every stuck message of the account."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"reintentados":{"type":"number"},"omitidos":{"type":"number","description":"Stuck messages left as they were."},"aviso":{"type":"string"}},"required":["reintentados","omitidos","aviso"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_calendar":{"post":{"operationId":"view_calendar","summary":"Shows the calendar for a SPECIFIC period — one day, one week, one month — with the tasks and the critical loan dates that fall in it, grouped day by day, plus the link to that same view of the calendar screen","description":"Shows the calendar for a SPECIFIC period — one day, one week, one month — with the tasks and the critical loan dates that fall in it, grouped day by day, plus the link to that same view of the calendar screen. Use it when the user names a period instead of asking what is pending: \"what does the week of the 15th look like\", \"show me October\", \"what do I have on Tuesday\", \"open the calendar on the week view\", \"how does next month look\". For the plain to-do list (\"what is overdue\", \"what is pending\") use view_tasks; to create or close a task use save_task; to change a loan date use update_loan.","tags":["tasks"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"view":{"anyOf":[{"type":"string","enum":["day","week","month","agenda"]},{"type":"null"}],"description":"Which view of the calendar: 'day', 'week' (Monday to Sunday), 'month', or 'agenda' (the month as a plain list of what is coming). Null means 'week'."},"date":{"type":["string","null"],"description":"The day to center the view on, as YYYY-MM-DD. For a week, any day inside it; for a month, any day of it. Null means today."}},"required":["view","date"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"vista":{"type":"string","enum":["day","week","month","agenda"]},"titulo":{"type":"string"},"desde":{"type":"string"},"hasta":{"type":"string"},"enlace":{"type":"string"},"resumen":{"type":"string"},"dias":{"type":"array","items":{"type":"object","properties":{"fecha":{"type":"string"},"dia":{"type":"string"},"eventos":{"type":"array","items":{"type":"object","properties":{"que":{"type":"string"},"de_quien":{"type":["string","null"]},"clase":{"type":"string","enum":["task","loan_date"]},"dias":{"type":"number"},"vence":{"type":"boolean"},"hecha":{"type":"boolean"},"tarea_id":{"type":["number","null"]},"prestamo_id":{"type":["number","null"]}},"required":["que","de_quien","clase","dias","vence","hecha","tarea_id","prestamo_id"],"additionalProperties":false}}},"required":["fecha","dia","eventos"],"additionalProperties":false}}},"required":["vista","titulo","desde","hasta","enlace","resumen","dias"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_tasks":{"post":{"operationId":"view_tasks","summary":"Lists tasks AND the critical loan dates falling in the same window — closings, commitment dates, and rate lock, credit or pre-approval expirations","description":"Lists tasks AND the critical loan dates falling in the same window — closings, commitment dates, and rate lock, credit or pre-approval expirations. Use it when the user asks \"what do I have today\", \"what is on my calendar this week\", \"what is overdue\", \"what is closing soon\", \"is any lock about to expire\", or wants the to-dos of one loan, one contact, or one team member. Filters combine: due window, loan or contact as a phrase (\"the Garcia file\", \"Maria\"), assignee (\"me\", a name, an email). By default it shows pending tasks only; ask for done or cancelled explicitly. It also returns the sections the list is grouped into and which section each task is in. To create or change a task use save_task; to change the sections themselves use set_task_sections; to change a loan date use update_loan.","tags":["tasks"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"due":{"type":"string","enum":["today","overdue","week","all"],"description":"Due window: 'today' (due today or earlier), 'overdue' (past due), 'week' (next 7 days), 'all'. Default 'all'."},"status":{"type":"string","enum":["pending","done","cancelled","all"],"description":"Which tasks. Default 'pending'."},"loan":{"type":"string","description":"Only tasks of this loan, as the user says it."},"contact":{"type":"string","description":"Only tasks about this person, as the user says it."},"assignee":{"type":"string","description":"Only tasks assigned to this member: \"me\", a name or email."},"section":{"type":"string","description":"Only tasks in this section of the list, by name. Use \"none\" for the ones that are not in any section."},"loan_dates":{"type":"boolean","description":"Whether to also return the critical loan dates in the window (closing, commitment, lock / credit / pre-approval expiration). Default true. Set false when the user asks strictly for to-dos."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"resumen":{"type":"string"},"tareas":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"titulo":{"type":"string"},"estado":{"type":"string"},"vence_el":{"type":["string","null"]},"vencida":{"type":"boolean"},"descripcion":{"type":["string","null"]},"subtareas":{"type":"array","items":{"type":"object","properties":{"texto":{"type":"string"},"hecha":{"type":"boolean"}},"required":["texto","hecha"],"additionalProperties":false}},"prestamo":{"type":["string","null"]},"contacto":{"type":["string","null"]},"asignado_a":{"type":["string","null"]},"seccion":{"type":["string","null"]},"revisada":{"type":"boolean"}},"required":["id","titulo","estado","vence_el","vencida","descripcion","subtareas","prestamo","contacto","asignado_a","seccion","revisada"],"additionalProperties":false}},"fechas":{"type":"array","items":{"type":"object","properties":{"prestamo_id":{"type":"number"},"prestamo":{"type":"string"},"que":{"type":"string"},"fecha":{"type":"string"},"vence":{"type":"boolean"},"dias":{"type":"number"}},"required":["prestamo_id","prestamo","que","fecha","vence","dias"],"additionalProperties":false}},"secciones":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"nombre":{"type":"string"}},"required":["id","nombre"],"additionalProperties":false}}},"required":["resumen","tareas","fechas","secciones"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/save_task":{"post":{"operationId":"save_task","summary":"Creates or updates a task","description":"Creates or updates a task. Use it when the user wants to remember to do something (\"remind me to call Maria on Friday\"), completes one (\"done with the CD review\"), cancels one, edits its title, due date or details, adds or checks off subtasks, ties it to a loan or contact, assigns it to a team member, moves it into or out of a section, or reorders it — to the top or bottom of its list, or right above or below another task (\"put the appraisal call under the CD review\"), which is what dragging a card by its grip does. To create, give a title; to change an existing one, name it by its id or by words from its title. To list tasks use view_tasks; to delete one for good use delete_task; to create, rename or reorder the sections themselves use set_task_sections.","tags":["tasks"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"task":{"type":"string","description":"The existing task to change: its id, or words from its title. Leave empty to create a new one."},"title":{"type":"string","maxLength":200,"description":"The task title. Required when creating; renames when editing."},"description":{"type":"string","maxLength":4000,"description":"Longer detail."},"due":{"type":"string","description":"When it is due, ISO (YYYY-MM-DD or full timestamp). 'none' clears it."},"status":{"type":"string","enum":["pending","done","cancelled"],"description":"Set to 'done' to complete, 'cancelled' to cancel, 'pending' to reopen."},"subtasks":{"type":"array","items":{"type":"object","properties":{"text":{"type":"string"},"done":{"type":"boolean"}},"required":["text","done"],"additionalProperties":false},"description":"Replaces the whole subtask list. To add some, use add_subtasks."},"add_subtasks":{"type":"array","items":{"type":"string"},"description":"Subtasks to append, unchecked."},"check_subtask":{"type":"string","description":"Words from one subtask to mark as done."},"loan":{"type":"string","description":"The loan this task is about, as the user says it."},"contact":{"type":"string","description":"The person this task is about, as the user says it."},"assignee":{"type":"string","description":"Who should do it: \"me\", a name, or an email. The person gets a notification in the app unless they assigned it to themselves."},"section":{"type":"string","description":"Which section of the task list it belongs to, by name (\"Follow-ups\"). Use \"none\" to take it out of every section. The section is created if it does not exist yet."},"position":{"type":"string","enum":["top","bottom"],"description":"Where inside its section: 'top' when the user wants it first (\"put it at the top\"), 'bottom' otherwise. Default: bottom."},"after":{"type":"string","description":"Put it right BELOW this other task — its id, or words from its title. Use it for \"move it under the appraisal follow-up\". With no section given it lands in that task's section."},"before":{"type":"string","description":"Put it right ABOVE this other task — its id, or words from its title. Use it for \"move it above the CD review\"."},"reviewed":{"type":"boolean","description":"Marks a task assigned to the caller as already seen, which clears it from their sidebar badge. Use it when they say \"got it\" or \"I already saw that one\"."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"number"},"titulo":{"type":"string"},"estado":{"type":"string"},"vence_el":{"type":["string","null"]},"seccion":{"type":["string","null"]},"resumen":{"type":"string"}},"required":["id","titulo","estado","vence_el","seccion","resumen"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/delete_task":{"post":{"operationId":"delete_task","summary":"Permanently deletes a task","description":"Permanently deletes a task. Use it ONLY for one created by mistake or a duplicate. A task that was decided against is NOT deleted — set its status to 'cancelled' with save_task so it stays on the record. Ask the user to confirm before calling with confirmar: true.","tags":["tasks"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"task":{"type":"string","minLength":1,"description":"Which task: its id, or words from its title."},"confirmar":{"type":"boolean","description":"Must be true to delete. Call it first with false to see which task it would be, show that to the user, and only then call with true."}},"required":["task","confirmar"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"borrada":{"type":"boolean"},"id":{"type":"number"},"titulo":{"type":"string"},"resumen":{"type":"string"}},"required":["borrada","id","titulo","resumen"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/set_task_sections":{"post":{"operationId":"set_task_sections","summary":"Creates, renames, deletes or reorders the sections that group the task list (\"Follow-ups\", \"Closings this week\")","description":"Creates, renames, deletes or reorders the sections that group the task list (\"Follow-ups\", \"Closings this week\"). Use it when the user wants to organise their tasks by category, says \"add a section\", \"rename that group\", \"get rid of that section\" or \"move Closings to the top\". Deleting a section does NOT delete its tasks: they go back to being unsectioned. To put a task into a section use save_task with section; to see the sections use view_tasks.","tags":["tasks"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"add":{"type":"array","items":{"type":"string","minLength":1,"maxLength":60},"description":"Section names to create, in order. Existing ones are left alone."},"rename":{"type":"array","items":{"type":"object","properties":{"from":{"type":"string","minLength":1},"to":{"type":"string","minLength":1,"maxLength":60}},"required":["from","to"],"additionalProperties":false},"description":"Sections to rename. \"from\" is the current name, as the user says it."},"remove":{"type":"array","items":{"type":"string","minLength":1},"description":"Sections to delete, by name. Their tasks survive, unsectioned. Confirm with the user first — this cannot be undone."},"order":{"type":"array","items":{"type":"string","minLength":1},"description":"The full list of section names in the order they should appear, top first. Anything left out keeps its current place at the end."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"secciones":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"nombre":{"type":"string"}},"required":["id","nombre"],"additionalProperties":false}},"resumen":{"type":"string"}},"required":["secciones","resumen"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/production_report":{"post":{"operationId":"production_report","summary":"Production report for a period: how many loans closed, total volume, and estimated commission, compared against the previous equivalent period","description":"Production report for a period: how many loans closed, total volume, and estimated commission, compared against the previous equivalent period. Use it when the user asks \"how did I do this month\", \"what did I close in Q2\", \"my production last year\", or any question about closings, volume, or commissions over time. For who referred the business use referral_report; for how long stages take use stage_timing_report.","tags":["insights"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"period":{"type":"string","description":"The period as the user says it: \"this month\", \"last quarter\", \"Q2 2026\", \"March\", \"2025\", \"last 30 days\". Defaults to this month."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"period":{"type":"object","properties":{"label":{"type":"string"},"from":{"type":"string"},"to":{"type":"string"}},"required":["label","from","to"],"additionalProperties":false},"closings":{"type":"number"},"volume":{"type":"number"},"estimated_commission":{"type":"number"},"closings_without_commission_data":{"type":"number"},"loans":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"borrower":{"type":["string","null"]},"address":{"type":["string","null"]},"amount":{"type":["number","null"]},"closed_on":{"type":"string"},"estimated_commission":{"type":["number","null"]}},"required":["id","borrower","address","amount","closed_on","estimated_commission"],"additionalProperties":false}},"previous":{"type":"object","properties":{"label":{"type":"string"},"from":{"type":"string"},"to":{"type":"string"},"closings":{"type":"number"},"volume":{"type":"number"},"estimated_commission":{"type":"number"}},"required":["label","from","to","closings","volume","estimated_commission"],"additionalProperties":false},"change":{"type":"object","properties":{"closings":{"type":"number"},"volume":{"type":"number"},"volume_pct":{"type":["number","null"]}},"required":["closings","volume","volume_pct"],"additionalProperties":false}},"required":["period","closings","volume","estimated_commission","closings_without_commission_data","loans","previous","change"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/referral_report":{"post":{"operationId":"referral_report","summary":"Referral report for a period: which sources brought the business — deals, volume, and conversion per source","description":"Referral report for a period: which sources brought the business — deals, volume, and conversion per source. Use it when the user asks \"who is sending me business\", \"which realtor referred the most this quarter\", \"where are my deals coming from\", or wants to know whom to thank or nurture. For totals closed use production_report.","tags":["insights"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"period":{"type":"string","description":"The period as the user says it: \"this month\", \"last quarter\", \"Q2 2026\", \"2025\", \"last 90 days\". Defaults to this month."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"period":{"type":"object","properties":{"label":{"type":"string"},"from":{"type":"string"},"to":{"type":"string"}},"required":["label","from","to"],"additionalProperties":false},"sources":{"type":"array","items":{"type":"object","properties":{"source":{"type":"string"},"contact_id":{"type":["number","null"]},"deals":{"type":"number"},"volume":{"type":"number"},"won":{"type":"number"},"conversion_pct":{"type":["number","null"]}},"required":["source","contact_id","deals","volume","won","conversion_pct"],"additionalProperties":false}},"total_deals":{"type":"number"},"deals_without_source":{"type":"number"}},"required":["period","sources","total_deals","deals_without_source"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/stage_timing_report":{"post":{"operationId":"stage_timing_report","summary":"How long each pipeline stage typically takes, averaged from this account's closed loans, and which active loans are currently taking longer than that typical time","description":"How long each pipeline stage typically takes, averaged from this account's closed loans, and which active loans are currently taking longer than that typical time. Use it when the user asks \"how long does underwriting take me\", \"where do my loans slow down\", \"which files are behind schedule\", or wants process insights. For a plain list of stalled files (fixed 7-day flag) use view_pipeline.","tags":["insights"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"stages":{"type":"array","items":{"type":"object","properties":{"stage":{"type":"string"},"label":{"type":"string"},"average_days":{"type":"number"},"samples":{"type":"number"}},"required":["stage","label","average_days","samples"],"additionalProperties":false}},"outliers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number"},"borrower":{"type":["string","null"]},"address":{"type":["string","null"]},"stage":{"type":"string"},"label":{"type":"string"},"days_in_stage":{"type":"number"},"average_days":{"type":"number"}},"required":["id","borrower","address","stage","label","days_in_stage","average_days"],"additionalProperties":false}},"closed_loans_measured":{"type":"number"},"note":{"type":["string","null"]}},"required":["stages","outliers","closed_loans_measured","note"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/weekly_summary":{"post":{"operationId":"weekly_summary","summary":"The weekly summary email, generated on demand: what moved stages this week, which documents came in, what is stalled, what closes in the next two weeks, and overdue tasks — as ready-to-send text","description":"The weekly summary email, generated on demand: what moved stages this week, which documents came in, what is stalled, what closes in the next two weeks, and overdue tasks — as ready-to-send text. Use it when the user asks \"give me my weekly summary\", \"how was my week\", \"what happened this week\", or wants a recap to forward. The same summary goes out automatically by email every week.","tags":["insights"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"summary":{"type":"string"},"data":{"type":"object","additionalProperties":{}}},"required":["summary","data"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_permissions":{"post":{"operationId":"view_permissions","summary":"Shows what each role in this account can do (read, edit files, send messages, delete, manage the account) and which columns each role cannot see","description":"Shows what each role in this account can do (read, edit files, send messages, delete, manage the account) and which columns each role cannot see. Use it when the user asks \"what can my assistant do\", \"why can't I see the rate\", \"what is hidden from the processor\", or before changing access with set_column_access.","tags":["equipo"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"roles":{"type":"array","items":{"type":"object","properties":{"rol":{"type":"string"},"label":{"type":"string"},"puede":{"type":"array","items":{"type":"string"}},"columnas_ocultas":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label"],"additionalProperties":false}}},"required":["rol","label","puede","columnas_ocultas"],"additionalProperties":false}},"columnas":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label"],"additionalProperties":false}}},"required":["roles","columnas"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/set_column_access":{"post":{"operationId":"set_column_access","summary":"Hides or shows specific columns for a role (assistant or loan officer), across the lists and the loan file","description":"Hides or shows specific columns for a role (assistant or loan officer), across the lists and the loan file. Use it when the user says \"assistants should not see the rate\", \"hide the credit score from my processor\", \"let loan officers see compensation again\". Only the account owner can do this. To see what is hidden today, use view_permissions.","tags":["team"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rol":{"type":"string","enum":["assistant","loan_officer"],"description":"Whose access changes. The owner always sees everything."},"esconder":{"type":"array","items":{"type":"string"},"description":"Column ids to hide from that role. Empty array if none."},"mostrar":{"type":"array","items":{"type":"string"},"description":"Column ids to give back to that role. Empty array if none."}},"required":["rol","esconder","mostrar"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"rol":{"type":"string"},"label":{"type":"string"},"columnas_ocultas":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label"],"additionalProperties":false}}},"required":["rol","label","columnas_ocultas"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_join_link":{"post":{"operationId":"view_join_link","summary":"Shows the public sign-up link of this account: the URL to put in an email signature or an Instagram bio, whether it is on, and what the page says","description":"Shows the public sign-up link of this account: the URL to put in an email signature or an Instagram bio, whether it is on, and what the page says. Use it when the user asks \"what is my link\", \"give me my sign-up link\", \"is my public page on\", or before changing it with set_join_link.","tags":["equipo"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":["string","null"]},"nombre":{"type":["string","null"]},"activo":{"type":"boolean"},"titular":{"type":["string","null"]},"subtitulo":{"type":["string","null"]},"gracias":{"type":["string","null"]},"leads":{"type":"number"}},"required":["url","nombre","activo","titular","subtitulo","gracias","leads"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/set_join_link":{"post":{"operationId":"set_join_link","summary":"Turns the public sign-up link on or off, changes its name (the last part of the URL) and the words on the page","description":"Turns the public sign-up link on or off, changes its name (the last part of the URL) and the words on the page. Use it when the user says \"turn on my public link\", \"my link should be maria-garcia\", \"change the headline\", \"turn off the sign-up page\". Only the account owner. To see the current link use view_join_link.","tags":["equipo"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"nombre":{"type":["string","null"],"description":"The last part of the URL (/join/<name>), as the user says it — it gets cleaned up into a link-safe name. Null to leave it as is."},"activo":{"type":["boolean","null"],"description":"true turns the page on, false turns it off. Null leaves it."},"titular":{"type":["string","null"],"description":"Headline of the page. Null leaves it as is."},"subtitulo":{"type":["string","null"],"description":"The line under the headline. Null leaves it as is."},"gracias":{"type":["string","null"],"description":"What the person sees after sending the form. Null leaves it as is."}},"required":["nombre","activo","titular","subtitulo","gracias"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":["string","null"]},"nombre":{"type":["string","null"]},"activo":{"type":"boolean"}},"required":["url","nombre","activo"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/view_team":{"post":{"operationId":"view_team","summary":"Lists the team of this account: the owner plus every invited assistant, with name, email, and role","description":"Lists the team of this account: the owner plus every invited assistant, with name, email, and role. Use it when the user asks \"who is on my team\", \"who has access to my account\", or before inviting, removing, or changing the role of someone, to confirm who is already in. To change what someone is, use set_teammate_role.","tags":["team"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"team":{"type":"array","items":{"type":"object","properties":{"name":{"type":["string","null"]},"email":{"type":["string","null"]},"role":{"type":"string","enum":["owner","assistant","loan_officer"]},"role_label":{"type":"string"},"since":{"type":["string","null"]}},"required":["name","email","role","role_label","since"],"additionalProperties":false}}},"required":["team"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/invite_teammate":{"post":{"operationId":"invite_teammate","summary":"Invites a person by email to join this account: they get full day-to-day access (contacts, pipeline, documents) but cannot manage the team or the settings","description":"Invites a person by email to join this account: they get full day-to-day access (contacts, pipeline, documents) but cannot manage the team or the settings. Only the account owner can do this. Use it when the user says \"invite my assistant\", \"add maria@x.com to my team\", \"give my processor access\", \"invite John as a loan officer\". Returns the invitation link too, in case the email does not arrive.","tags":["team"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"The email of the person to invite, e.g. \"maria@gmail.com\"."},"role":{"type":"string","enum":["assistant","loan_officer"],"description":"What they are on the team: \"assistant\" (default — a processor or assistant helping with the book) or \"loan_officer\" (another originator working the same pipeline). Only set it if the user says which one."}},"required":["email"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"role":{"type":"string"},"invite_link":{"type":"string"},"email_queued":{"type":"boolean"},"note":{"type":"string"}},"required":["email","role","invite_link","email_queued","note"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/invitation_link":{"post":{"operationId":"invitation_link","summary":"Lists the invitations that are still waiting for an answer and, for one person, issues a fresh invitation link to share by hand","description":"Lists the invitations that are still waiting for an answer and, for one person, issues a fresh invitation link to share by hand. Use it when the user says \"the invitation email never arrived\", \"resend the invite to maria\", \"give me the link for my assistant\", or asks who has not accepted yet. It also reports whether the account email channel is on: while it is off no invitation email goes out at all, and the link is the only way in — turn it back on with set_channel. Issuing a new link invalidates the previous one for that person. Only the account owner, signed in from their own session.","tags":["team"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"person":{"type":"string","description":"Who to issue the link for — their email or their name, as the user says it (\"maria@gmail.com\", \"Maria\"). Leave it out to just see who has not accepted yet, without issuing anything."}},"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"email_channel_on":{"type":"boolean"},"invitations":{"type":"array","items":{"type":"object","properties":{"name":{"type":["string","null"]},"email":{"type":["string","null"]},"role":{"type":"string"},"role_label":{"type":"string"},"invited_on":{"type":["string","null"]}},"required":["name","email","role","role_label","invited_on"],"additionalProperties":false}},"invite_link":{"type":["string","null"]},"invite_link_for":{"type":["string","null"]},"note":{"type":"string"}},"required":["email_channel_on","invitations","invite_link","invite_link_for","note"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/set_teammate_role":{"post":{"operationId":"set_teammate_role","summary":"Changes what someone already on the team is: an assistant (a processor or assistant helping with the book) or a loan officer (another originator working the same pipeline)","description":"Changes what someone already on the team is: an assistant (a processor or assistant helping with the book) or a loan officer (another originator working the same pipeline). Only the account owner can do this. Use it when the user says \"make Maria a loan officer\", \"John is my assistant, not an LO\", \"change Ana's role\". The owner's own role cannot be changed.","tags":["team"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"member":{"type":"string","minLength":1,"description":"The teammate, as the user says it: an email (\"maria@gmail.com\") or a name (\"Maria\")."},"role":{"type":"string","enum":["assistant","loan_officer"],"description":"What they become: \"assistant\" or \"loan_officer\". Ask the user if they did not say."}},"required":["member","role"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"member":{"type":"string"},"email":{"type":["string","null"]},"role":{"type":"string"},"role_label":{"type":"string"}},"required":["member","email","role","role_label"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}},"/api/v1/remove_teammate":{"post":{"operationId":"remove_teammate","summary":"Removes an assistant from this account by email or name, revoking their access immediately","description":"Removes an assistant from this account by email or name, revoking their access immediately. Only the account owner can do this. Use it when the user says \"remove maria from my team\", \"revoke access for x@y.com\", \"my assistant left\". The owner cannot be removed.","tags":["team"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"member":{"type":"string","minLength":1,"description":"The teammate, as the user says it: an email (\"maria@gmail.com\") or a name (\"Maria\")."}},"required":["member"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"responses":{"200":{"description":"The action ran","content":{"application/json":{"schema":{"type":"object","properties":{"removed":{"type":"string"},"email":{"type":["string","null"]}},"required":["removed","email"],"additionalProperties":false,"$schema":"http://json-schema.org/draft-07/schema#"}}}},"400":{"description":"The parameters are not valid"},"401":{"description":"Missing or invalid API key"},"404":{"description":"What you asked for was not found"},"500":{"description":"Server or provider error"}}}}}}