Environment: prod
Model Context Protocol server for SuperOffice CRM. Connects an MCP-aware AI client to SuperOffice search, contact, and identity tools.
There's currently a pilot program underway, and we're not enrolling any additional participants at this time. SuperOffice MCP server will be available on the 1st of October in SuperOffice CRM Suite Plus plan.
https://mcp.superoffice.com/mcp
Clients authenticate via SuperOffice OpenID Connect. The server proxies OIDC to SuperOffice and requires a valid access token on every request.
Download the bundle and open it in any MCPB-compatible MCP client for one-click installation and automatic OAuth login.
Download SuperOffice MCP bundle (.mcpb)Add this to your MCP client configuration (for example, Claude Desktop's mcpServers block):
{
"mcpServers": {
"superoffice": {
"url": "https://mcp.superoffice.com/mcp"
}
}
}
Step-by-step guides for connecting your AI client:
describe_custom_object — Return a paged, queryable schema view for a custom table.
The default view contains only direct fields and relation IDs. Use a
returned relation_prefix to inspect expanded relation fields without
flooding the model context with every related entity column. A relation's
$x_display_field is tenant-configured and may resolve to a raw ID rather
than a human-readable label.get_appointment — Returns the full appointment record plus href (direct CRM web UI link).
Prefer this over search_appointments when the appointment_id is already known.
Cross-reference IDs (contact_id, person_id, project_id, sale_id) are included
for navigating to linked entities.get_associate — Resolve a numeric associate (user) ID into a display identity: name,
email, initials, job title, and active status.
Use this whenever another tool's result surfaces a bare numeric
associate_id (e.g. get_company's associate_id, whoami's associate_id)
and the user needs something more meaningful than the number — a name
to display, an email to act on, etc.get_company — Returns the full company record plus `href` (direct CRM web UI link).
Prefer this over search_companies when the company_id is already known.get_custom_object_row — Read one custom-object row using the archive provider's normal JSON types.get_document_contents — Return a document's raw file contents (the original file) as a file/blob.
Fetches the actual bytes of a stored CRM document (Office file, PDF, or
email) by its `document_id` — call search_documents first to find the ID.
The result is an MCP-native file block: consume it directly to read or
analyze what the document contains, or hand it back to the user as the
original file.
To just read/summarize what a document says, prefer get_document_markdown
instead — markdown text is far cheaper to consume than raw file bytes. Use
this tool when the user needs the file itself (to download, or when the
format matters, e.g. an image or a template to reuse).
Documents whose content lives in SharePoint cannot be fetched with the
MCP's credentials and fail with an auth error; contents larger than 20 MB
are rejected rather than returned.get_document_markdown — Return a document's contents as markdown text — the cheap way to read what it says.
Fetches a stored CRM document (Office file, PDF, or email) by its
`document_id` — call search_documents first to find the ID — and converts
it to markdown, which costs far fewer tokens to consume than the original
file. Prefer this over get_document_contents whenever the user just wants
to know what a document contains; fall back to get_document_contents only
when the original file itself is needed (download, or a format markdown
can't represent, e.g. an image).
Documents whose content lives in SharePoint cannot be fetched with the
MCP's credentials and fail with an auth error. Unsupported file formats
(e.g. legacy .doc/.ppt, images) fail with a validation error.get_person — Returns the full person record plus `href` (direct CRM web UI link).
Prefer this over search_persons when the person_id is already known.get_project — Returns the full project record plus href (direct CRM web UI link).
Only use this over search_projects when the project_id is already known.
Use list_project_activities to fetch activities linked to the project.get_sale — Fetch the complete record for one sale by its numeric ID.
Returns all sale fields (heading, amount, earnings, probability, description,
status, stage, owner, linked company/person), plus HasGuide (whether a stage
guide exists) and href — a direct link to the sale in the CRM web UI. Include
the href when presenting a sale to the user so they can open it directly.
Call this before get_sale_guide to check HasGuide.get_sale_guide — Return the pipeline guide for a sale: current stage, next stage, and all
stages in order (each with its win probability), and href.
Only call this when get_sale returned HasGuide=true. If HasGuide is false,
no guide exists for this sale type — tell the user that instead of calling
this tool.get_ticket — Returns a ticket — its conversation, as message-thread summaries, newest first.
This is the ticket's actual content: what was said, and by whom. Call this
for any question about a ticket's messages, conversation, history, or who
said/replied what (e.g. "show me ticket 123", "message history", "what did
they say", "who has replied"). For the ticket's bookkeeping fields instead
(status, category, owner), call get_ticket_metadata.
messages are METADATA only — author, date, security level (Internal/External),
sentiment, attachment count, and badge (Reply/Forward/Comment/Incoming/Outgoing —
use this to see who has replied). It does NOT return the message text; the first
entry is the most recent message — use get_ticket_message with this
ticket_id and the message's ticket_message_id to read the full body.
Capped at `limit` (default 25, max 100). has_more=true means older messages
exist beyond that cap — since ordering is newest-first, what's missing is the
START of the thread (often the customer's original problem statement), not the
latest activity. Never present a capped list as the complete conversation; if
you need the exact total, call get_ticket_metadata's num_messages.get_ticket_message — Returns the full content of one ticket message: its plain-text body, plus
html_body only for HTML-only messages that have no plain-text body (empty
otherwise, to avoid sending the same content twice).
Use get_ticket first to find the ticket_id and ticket_message_id of the
message you want to read — never guess a message_id. An unknown message_id
raises a ToolError listing that ticket's valid message IDs so you can retry
with a real one; a message_id that belongs to a different ticket raises a
mismatch error naming the ticket that actually owns it.get_ticket_metadata — Returns a ticket's bookkeeping record — status, category, owner, counts,
cross-reference IDs, and href. This is NOT the ticket's conversation and
contains no message text; for what was said or who replied, call
get_ticket(ticket_id) instead.
Prefer this over search_tickets when the ticket_id is already known.
Cross-reference IDs (contact_id, sale_id, project_id) are included
for navigating to linked entities.list_appointment_types — List this tenant's appointment types (meeting, vacation, phone call, to-do, ...) with their ids.
Call this before search_appointments' `appointment_type_ids` whenever the user names
a kind of appointment, e.g. "my vacations", "phone calls this week" — match the
wanted kind by name here and pass every matching id on. Type names and how many exist vary by
tenant and language, so don't assume a name or id from another tenant or a prior
conversation still applies here. record_type tells meetings (Appointment) apart
from phone calls (Phone) and to-dos (ToDo).list_contact_activities — Returns activities (appointments, documents, chats, mailings, form submissions) for a company.
Use get_company or search_companies to find the contact_id first.
Results are sorted newest first.list_custom_objects — List custom tables configured for the authenticated SuperOffice tenant.
Call this whenever a user refers to an object, record, or entity type that doesn't
match any of the standard entities (company, person, project, sale, ticket,
appointment) — before assuming it doesn't exist or answering from general knowledge.
Cheap: returns table names and flags only, no row data. An empty result means this
tenant has no custom objects; don't call it again in the same conversation once you
know that.list_document_templates — List this tenant's document templates, with their functional grouping.
Call this before search_documents' `document_template_ids` for the templates' ids, or
whenever you need to know what kinds of documents/quotes/emails this tenant's
templates represent — template names and how many exist per kind vary by tenant
and language, so don't assume a name (or id) from another tenant or a prior
conversation still applies here.
For "all quote documents" specifically, prefer search_documents(quote_only=true)
directly — it already covers every kind='quote' template without needing their
names first. Use this tool when you need the template names/ids themselves, or
a kind other than quote.list_person_activities — Returns activities (appointments, documents, chats, mailings, form submissions) for a person.
Use get_person or search_persons to find the person_id first.
Results are sorted newest first.list_project_activities — Returns activities (appointments, documents, mailings) for a project.
Use search_projects or get_project to find the project_id first.
Results are sorted newest first.list_project_members — List the members (people involved) on a project.
The first entry (when present) is the project-card owner (responsible associate),
flagged is_root=True — the implicit "root" member that isn't an explicit
ProjectMember row. Treat it as an important member even though it comes from the
project card rather than the member list. The remaining entries are the explicit
members, each with name, role, linked person/company, and href — a link to the
member (person) in the CRM web UI.
Note: the root member is a forward-only annotation. Because it has no ProjectMember
row, list_projects_for_member(person) will NOT return this project for the root
member — only for the explicit members shown here.
Get the project_id from search_projects or get_project first.list_projects_for_member — Find the projects a person is currently a project-member of (reverse of list_project_members).
Returns membership only — projects where the person is an actual project-member,
NOT projects where they are merely the project-card owner. One row per project with
project_name, project_id, the role, the project's status name, its end_date, and
href (a direct link to the project).
Asymmetry with list_project_members: this excludes the project-card owner (the
is_root=True member there). So a person shown ONLY as the root member of a project
will not have that project returned here — the root member is a forward-only
annotation, not a symmetric membership link. Explicit members round-trip both ways.
These are the person's current memberships — SuperOffice doesn't retain
removed/former members, so a project the person was once a member of but has since
been removed from can't be listed.
No active/ended filter: SuperOffice project status is a tenant-configured list with
no system "completed" flag (status ids and names differ per database and language),
so the server can't classify active vs ended across tenants. Each row instead carries
the raw status (in the tenant's language, e.g. "Avsluttet" or "Completed") and
end_date — filter on those yourself (e.g. treat a past end_date as expired, or a
status you recognise as finished).
Resolve the person first: search_persons by name, or whoami() for
"projects I'm a member of".list_sale_activities — List activities (appointments and documents) linked to a sale, newest first.
Returns type, date, heading, owner, and completion status per activity.
Get the sale_id from search_sales or get_sale first.list_sale_stakeholders — List the stakeholders (people involved) on a sale.
The first entry (when present) is the sale-card's main contact/person, flagged
is_root=True — the implicit "root" stakeholder that isn't an explicit
SaleStakeholder row. Treat it as an important stakeholder even though it comes
from the sale card rather than the stakeholder list. The remaining entries are
the explicit stakeholders, each with name, role (e.g. decision maker), linked
person/company, email, phone, any comment, and href — a link to the stakeholder
(person, or company) in the CRM web UI.
These are the sale's current stakeholders — SuperOffice doesn't retain
removed/former stakeholders, so past stakeholders can't be listed.
Get the sale_id from search_sales or get_sale first.list_sales_for_stakeholder — Find the sales a person or company is currently a stakeholder on (the reverse of
list_sale_stakeholders).
Provide exactly one of:
- person_id — "which deals is <person> a stakeholder on".
- company_id — "which deals is <company> a stakeholder on" — includes both the
company itself as a stakeholder and any of its people as stakeholders.
Returns one row per sale with sale_name, sale_id, status, the role (e.g. decision
maker), the linked company, any comment, and href — a direct link to the sale in
the CRM web UI (include it when presenting a sale).
Only current stakeholders are returned — SuperOffice keeps only present
stakeholder links, so someone removed as a stakeholder won't appear and there is no
way to list past/former stakeholders. The `status` filter scopes by the sale's own
status (open vs closed/won/lost), not by stakeholder history: "all" (default),
"open", or "closed" — a person can still be a current stakeholder on a closed sale.
Resolve the entity first: search_persons / search_companies by name, or whoami()
for "sales I'm a stakeholder on". Note this is distinct from
search_sales(person_id=...), which matches the sale's primary linked person.list_user_activities — Returns activity-archive items (follow-ups, tasks, documents) for a user.
Do NOT use for "diary", "calendar", "schedule", or "appointments" queries — use search_appointments instead.
Call whoami() first to get the associate_id for the current user.
Results are sorted newest first.search_appointments — Search for appointments (meetings, calls) in the CRM database.
Use this for any query about a user's diary, calendar, schedule, or appointments —
this is the authoritative source for what is booked in a user's diary.
Returns up to `limit` matching results.
For a text search, prefer title or agenda when the caller cares about one of them
specifically. text matches title OR agenda as two independent checks, so it can match on
either field — but "begins"/"is" on either one typically miss a real match against agenda
content (a <p>-wrapped agenda renders with a leading line break); match_type="contains" is
the reliable way to reach agenda text, via text or agenda directly. Use text only for a
combined search where either field may match.
"my" / "our" queries — call whoami() first:
- "my appointments" / "appointments I own" / "my calendar"
-> pass associate_id from whoami() as our_contact_id.
- "appointments at my company" -> pass company_id from whoami().
A colleague's appointments ("Jens Glattetre's appointments") -> search_associates(name=...),
then pass associate_id as our_contact_id. Appointments *with* a customer ("appointments
with Jens Glattetre") -> search_persons(), then pass person_id.
Queries naming a kind of appointment ("my vacations", "phone calls this week",
"meetings with X") -> list_appointment_types(), keep every type whose name matches
the kind (a tenant may have several, e.g. 'Phone in' and 'Phone out'), then ONE
search_appointments(appointment_type_ids=[<all kept ids>]) combined with the other
filters (our_contact_id, date range) — not one call per type, which would give
separately sorted and limited pages. Do this without asking the user to confirm.
Do not replace it with a text/title search for the kind word: that finds only
appointments whose title happens to mention it. The result's `type` field is that
same tenant-specific name, so filter server-side rather than matching it.
A row with is_recurrence true is one occurrence of a repeating series. update_appointment
refuses those, so do not offer to edit or complete them one by one — point the user to
the appointment's href in SuperOffice, where the scope (this occurrence or the whole
series) can be chosen.search_associates — Find associates (CRM users: owners, sales reps, "our contact") by name and return their associate_id.
Use when a request names a colleague as the owner or responsible user,
e.g. "Jens Glattetre's appointments", "sales owned by Jens", "tickets assigned to Jens".
Pass the returned associate_id as our_contact_id to the search tools (or as
service_contact_id on search_companies), or as associate_id to list_user_activities
and get_associate.
Do NOT use for customers or other contacts at a company ("appointments with Jens",
"people at Acme") -- those are persons: use search_persons and filter on person_id.
For the current user ("my ...") use whoami instead.
Matches are ranked best first and may include retired users. Several matches means
several users share the name: ask the user which one they mean (get_associate shows
whether each one is still active). If the right person is not among them, retry with
their full name. An empty list means no associate has that name.search_companies — Search for companies in the CRM database.
Returns up to `limit` matching companies; use `get_company` with the returned `company_id` for the
full record.
Pagination: a response can be truncated before `limit` companies either because `limit` was reached
or because the response grew too large to return in one page — either way, `has_more=true` means
more companies exist. When `has_more` is true, `next_offset` is set: pass it back verbatim as the
next call's `offset` to fetch the next page. Do not derive the next offset yourself from `limit` or
the number of companies returned; that guess is wrong whenever a page was cut short by size.
"my" / "our" queries — call whoami() first, then choose the right parameter:
- "my company" (the company the user belongs to) -> pass company_id from whoami().
- "my companies" / "companies I manage" (companies assigned to the user)
-> pass associate_id from whoami() as our_contact_id.
- "companies Jens manages" (a named colleague) -> search_associates(name="Jens"),
then pass associate_id as our_contact_id (or as service_contact_id).search_custom_object_rows — Search rows in a tenant-defined custom table.
Call list_custom_objects first, then describe_custom_object to discover
valid columns and their filter/sort capabilities.
Pagination: a response can be truncated before `limit` rows either because `limit` was reached or
because the response grew too large to return in one page -- either way, `has_more=true` means more
rows exist. When `has_more` is true, `next_offset` is set: pass it back verbatim as the next call's
`offset` to fetch the next page. Do not derive the next offset yourself from `limit` or the number of
rows returned; that guess is wrong whenever a page was cut short by size.search_documents — Search for documents in the CRM database.
A document is anything stored in CRM as a file — Office files (Word,
Excel, PowerPoint), PDFs, and emails.
`is_email`, `document_template_ids`, and `quote_only` all narrow by document kind,
and picking the wrong one silently loses rows. Use `is_email=true` for "any
email" (covers every email template at once); use `quote_only=true` for "any
quote/quote-related document" (covers every quote template at once). Both
`is_email` and `quote_only` also accept `false` to mean "everything except that
category" — omit the parameter entirely for no restriction. Use
`document_template_ids` for any other kind, passing every tenant-defined template
that matches the kind in one call. `document_template_ids` and `quote_only` cannot
be combined — passing both raises instead of silently AND-ing the two into a filter
that matches nothing.
There is no separate get_document tool: pass `document_id` to look up one
document by ID. The company/person/sale/project filters match documents
linked to that entity, not its sender or author.
`date` is the document's own date (for an email, when it was sent), not when it
was saved into CRM; that is `registeredDate`. Sort on the one the user means (see
`sort_by`) and present that same field, or the listed order will look wrong.
Lookup patterns — call the named tool first to get the required ID:
- "documents for company X" -> search_companies(name="X"), then company_id.
- "my documents" -> whoami(), then our_contact_id.
- "Jens's documents" (a named colleague) -> search_associates(name="Jens"), then our_contact_id.
- "all <kind> documents" (fax, letter, note, ...; not email/quote) ->
list_document_templates(), keep every template whose name or
record_type matches the kind (a tenant often has several, e.g. a
domestic and an international fax template), then ONE
search_documents(document_template_ids=[<all kept ids>]) — not one call
per template, which would give separately sorted and limited pages.
Do this without asking the user to confirm, and never drop a matching
template. Do not replace it with a subject/filename search for the
kind word — that finds only documents whose title happens to mention
it and misses every document filed under the template.search_external_companies — Search EXTERNAL sources for companies — national business registries
and similar sources outside SuperOffice. This does NOT search the
tenant's own CRM contacts; use search_companies for that. Useful for
finding real, verified company data when the company may not already
exist as a CRM contact.search_persons — Search for persons/contacts in the CRM database.
Returns up to `limit` matching results; use `get_person` with the returned `person_id` for the full record.
"my" / "our" queries — call whoami() first:
- "people at my company" / "my colleagues" -> pass company_id from whoami().search_projects — Search for projects by name and/or owner.
At least one of name, our_contact_id, project_owner_full_name, or is_guided must be provided.
For 'list guided projects' / 'find guided projects' queries, set is_guided=True.
For one named owner, call search_associates(name=...) and pass associate_id as our_contact_id;
project_owner_full_name instead matches every owner whose name matches the text.
Returns matching projects with ID, name, type, status, dates, and owner.
Use get_project with the returned project_id for the full record.
Results are sorted by name.search_sales — Search sales (opportunities) in the CRM.
At least one filter parameter is required — a call with no filters fails.
Returns up to `limit` lightweight rows: sale_id, heading, amount (tenant
currency), close date, status, stage, owner, and linked company/person.
For the full record (description, earnings, probability, guide status,
web link) call get_sale with the returned sale_id.
Lookup patterns — resolve IDs with the named tool first:
- "sales for company X" -> search_companies(name="X"), pass contactId as company_id
- "my sales / sales I own" -> whoami(), pass associate_id as our_contact_id
- "sales owned by Jens" -> search_associates(name="Jens"), pass associate_id as our_contact_id
- "sales at my company" -> whoami(), pass company_id
- sale by name -> heading="..." (prefix match on the title)
If nothing matches, broaden the search: drop stage/status, widen the date
range, or shorten the heading prefix.search_tickets — Search for tickets (service requests) by structured criteria.
Use this when you already have a status, an owner, a category, a linked company
or person, or a known title prefix. For an open-ended question about what
tickets exist on a topic, phrased in the user's own words, use
search_tickets_freetext: it ranks matches across title, sender, author and
linked company/person/project, where this tool filters on the title column alone.
Returns up to `limit` matching results; use get_ticket with the returned
ticket_id to read the conversation, or get_ticket_metadata for its full
status/category/owner record.
Lookup patterns — call the named tool first to get the required ID:
- "tickets for company X" -> call search_companies(name="X"), then pass contact_id as company_id.
- "my tickets" / "tickets I own" -> call whoami(), then pass associate_id as our_contact_id.
- "tickets assigned to Jens" -> call search_associates(name="Jens"), then pass associate_id as
our_contact_id. A service user such as "Customer Service" is not found by name; use the
owned_by_associate_id from get_ticket_metadata of one of its tickets instead.
- "open tickets" / "closed tickets" -> pass status="open" or status="closed".
- "unassigned tickets" / "tickets with no owner" -> pass unassigned=true.
Do not combine with our_contact_id — a ticket can't be both unassigned and owned.
Closed tickets are excluded unless status="closed" is passed, so "are there any
unassigned tickets at all?" needs a second call with status="closed" to be sure.
- "tickets in category X" -> pass category="X" (name or numeric ID); subcategories are
included automatically, so a parent category also matches its children.search_tickets_freetext — Search tickets by topic, in the user's own words.
Use this for any open-ended question about what tickets exist ("are there
tickets about X?", "anything mentioning Y"), which is the common case. Use
search_tickets only when you already have structured criteria: a status, an
owner, a category, a linked company or person, or a known title prefix.
Scores and ranks results by relevance: the user's own tickets, recently
modified tickets, and word-boundary matches score higher.
Searches ticket METADATA only — title, sender, author, and linked
company/person/project. It does NOT search the ticket's message or conversation
history; to read a ticket's contents, use get_ticket with the returned ticket_id,
or get_ticket_metadata for its status/category/owner record.
Matching scope: all query words must match fields within the same ticket.
Cross-ticket matching is not supported — "Jostein WebTools" only matches if both
words appear on the same ticket.
Keep queries short and specific (a few words — names, company, subject). The query is
sent to SuperOffice exactly as given, so results match CRM Online; only empty or
excessively long queries are rejected. Matching is locale-sensitive; queries in the
tenant's language work best.
Returns up to `limit` ranked results; use get_ticket with the returned
ticket_id to read the conversation, or get_ticket_metadata for its
status/category/owner record.update_appointment — Update or reschedule an existing appointment, leaving everything else untouched.
Only the fields you supply are changed; anything omitted keeps its current value.
Values that already match are skipped, so calling this twice with the same values is
safe — the second call simply returns the stored appointment.
To clear title, location, agenda, or internal_notes, pass a single space.
agenda and internal_notes are HTML: send HTML (e.g. '<p>...</p>'), not plain text —
SuperOffice stores and returns them as HTML, and plain text is not converted for you.
internal_notes is never shown to meeting participants.
This overwrites existing data: the previous values are not recoverable through this
tool. Get the appointment_id from search_appointments or get_appointment first.
To move an appointment, supply start_date and end_date together. Times are local
wall-clock times in the CRM's own time zone: SuperOffice strips a 'Z' or a UTC offset
rather than converting it, so those are rejected here instead of being stored as a
different time than you meant. A range that does not end after it starts is also
rejected — the API would silently repair it by destroying the appointment's duration
or by discarding the new end time and reporting success anyway.
On a repeating appointment, updates are refused — no field can be changed through this
tool, not just the times. Change it in SuperOffice instead, where you can choose the
scope. Cannot reschedule an all-day event either. Cannot change the appointment type, or
the linked person, company, project, sale or owner. Documents and reports are rejected —
they share this endpoint but are not appointments.
Concurrent edits are last-writer-wins: the API offers no If-Match on this
endpoint, so a change made between the read and the patch is overwritten
without warning.update_company — Update fields on an existing company, leaving everything else untouched.
Only the fields you supply are changed; anything omitted keeps its current
value. Values that already match are skipped, so calling this twice with the
same values is safe — the second call simply returns the stored company.
To clear department or org_nr, pass a single space.
This overwrites existing data: the previous values are not recoverable through
this tool. A company is the parent record for its people, sales and activities,
so renaming one changes how all of them are found. Get the company_id from
search_companies or get_company first.
Concurrent edits are last-writer-wins: the API offers no If-Match on this
endpoint, so a change made between the read and the patch is overwritten
without warning.
Website, city, phone and email cannot be updated here — SuperOffice stores
several of each per company. Category, business, country and owner are not
updatable either; they are list-backed fields needing their own resolution step.update_person — Update fields on an existing person, leaving everything else untouched.
Only the fields you supply are changed; anything omitted keeps its current
value. Values that already match are skipped, so calling this twice with the
same values is safe — the second call simply returns the stored person.
To clear firstname, middle_name, lastname, title, description or mrmrs, pass a
single space. Firstname and lastname cannot both end up blank.
This overwrites existing data: the previous values are not recoverable through
this tool. Get the person_id from search_persons or get_person first.
`description` (also called "note" in the UI) is always replaced whole, never
appended. If the user's request implies adding or appending to existing text,
call get_person first, read the current description, and pass the combined
old-plus-new text here, with the new text on its own line (join with a single
newline). If the request implies replacing, setting, or clearing the text,
pass only the new value.
Concurrent edits are last-writer-wins: the API offers no If-Match on this
endpoint, so a change made between the read and the patch is overwritten
without warning.
Email address and phone numbers cannot be updated here — SuperOffice stores
several of each per person, so changing them needs more than a single value.
Position, category, and the owning company are also not updatable here; they
are list- or record-backed fields needing their own resolution step.update_project — Update fields on an existing project, leaving everything else untouched.
Only the fields you supply are changed; anything omitted keeps its current
value. Values that already match are skipped, so calling this twice with the
same values is safe — the second call simply returns the stored project.
To clear description, pass a single space.
This overwrites existing data: the previous values are not recoverable through
this tool. Get the project_id from search_projects or get_project first.
`description` is always replaced whole, never appended. If the user's request
implies adding or appending to existing text, call get_project first, read the
current description, and pass the combined old-plus-new text here, with the new
text on its own line (join with a single newline). If the request implies
replacing, setting, or clearing the text, pass only the new value.
Concurrent edits are last-writer-wins: the API offers no If-Match on this
endpoint, so a change made between the read and the patch is overwritten
without warning.
The project number is not updatable here — SuperOffice generates it. Project
type, status and dates are not updatable either; they need their own resolution
or read-side work first.update_sale — Update fields on an existing sale, leaving everything else untouched.
Only the fields you supply are changed; anything omitted keeps its current
value. Values that already match are skipped, so calling this twice with the
same values is safe — the second call simply returns the stored sale.
This overwrites existing data: the previous values are not recoverable through
this tool. Get the sale_id from search_sales or get_sale first.
Concurrent edits are last-writer-wins: the API offers no If-Match on this
endpoint, so a change made between the read and the patch is overwritten
without warning.
On a sale that has a quote, avoid `amount` and `earning` — SuperOffice
recalculates those from the quote's active alternative and will overwrite
whatever is set here. Change the quote instead.update_ticket — Update fields on an existing ticket, leaving everything else untouched.
Only the fields you supply are changed; anything omitted keeps its current
value. A value that already matches is skipped, so calling this twice with
the same value is safe.
This overwrites existing data: the previous value is not recoverable through
this tool.
Concurrent edits are last-writer-wins: the API offers no If-Match on this
endpoint, so a change made between the read and the patch is overwritten
without warning.
Status, category, priority, owner and other list-backed or workflow fields
are not updatable here; they need their own resolution step.whoami — Returns the authenticated user's identity. Call this first before any "my" or "our" query.
Use the returned values to drive other tools:
- company_id -> "my company" (get_company), "people at my company" (search_persons),
"appointments at my company" (search_appointments)
- associate_id -> pass as our_contact_id for "my companies" (search_companies),
"my appointments" (search_appointments), and likewise for search_sales,
search_projects, search_documents and search_tickets. For a named colleague
instead, get their associate_id from search_associates.
- person_id -> identifies the user as a CRM person
Also returns associate_name, email, role, group_id, ej_user_id, context_identifier.