# ContactManager SOAP API
Base URL: {BaseUrl}Api/ContactManager.asmx — WSDL at {BaseUrl}Api/ContactManager.asmx?WSDL.
Every method on the underlying ContactManager class is exposed here (this is the classic ASP.NET Web Services / ASMX surface) — this page is the complete method list. A subset of these are also exposed over REST/JSON; see REST.md for that subset's URL routes. Parameter names and behavior are identical between the two protocols since they're the same underlying method — SOAP just calls them as a SOAP action instead of a URL.
# Authentication & session
# Login(userName, password) → bool
Logs in. On success, sets the session/Forms-auth cookies (see README.md) and returns true.
- If
userNamecontains@and the environment hasEnableOffice365Loginenabled:passwordis treated as a Microsoft Graph bearer access token, validated against Graph/v1.0/me, and matched to an existing CM user. - Otherwise
passwordis a literal ContactManager password. - If already logged in when called, logs out the current session first.
# Logout() → bool
Also has a REST route — see REST.md.
Clears the session and signs out of Forms Authentication. Returns false if not authenticated to begin with.
# ChangeDefaultCulture(cultureCode) → void
Also has a REST route — see REST.md.
Switches the session's culture to cultureCode (case-insensitive match against the domain's registered cultures). Throws if the culture isn't registered.
# IsAuthenticated() → bool
Whether the current session is logged in.
# Fetch, search, and addressing-helper methods
These are shared 1:1 with the REST surface — same method names, same parameters, same return shapes, just invoked as SOAP actions instead of URL templates. See the corresponding tables in REST.md:
- Fetch — single record by ID
- Fetch — collections / related records
- Search
- Addressing / mail-merge helpers
GetCurrentUserName,GetCurrentContactID,GetCurrentUserLanguage(documented under Login in REST.md)
Plus a handful of SOAP-only read methods with no REST route:
| Method | Params | Returns | Notes |
|---|---|---|---|
GetValues | ID, fields | object[] | Also SOAP-reachable (also has a REST route, see REST.md) |
GetContactPostalAddressee/...2, GetContactVisitorsAddressee/...2, GetContactAddressee/...2, GetItemMailMerge/...2, GetSalutation, GetAttention | — | — | Also have REST routes, listed in REST.md |
GetUserNameForContact, GetUserIDForContact, GetContactByUserName, GetLanguages, GetCommunicationItems, GetLookupItem, GetLookupItems, GetAddresseeFormatTypes, GetAttendees/GetAttendees2 | — | — | Also have REST routes, listed in REST.md |
GetInternalItems, GetExternalItems/GetExternalItems2, GetItems, GetAllItems, GetItemsFromFolder, GetFoldersByName, GetRevision, GetFolders, GetSearchTypes, SearchMatters, SearchActiveMatters | — | — | Also have REST routes, listed in REST.md |
(The above are cross-referenced rather than duplicated here — every one of them is reachable via both SOAP and REST with identical parameters.)
# Write operations
Full behavioral detail for each method is documented here; most of these also have a REST route (JSON POST) — see REST.md for the exact route/params, or use the SOAP action name below directly. Only SetValue is SOAP-only with no REST route.
To update an existing record, always fetch it first: call the matching Get* method, change the properties on the object it returns, then pass that object to the Save* method. The fetched object carries the record's current VersionID, which the optimistic-concurrency check needs. A Get* call for a record that doesn't exist returns an empty object with an empty ID rather than an error, so the same call also tells you whether you are updating or creating. When creating a record the Get* call isn't needed — set ID to the CM ID, to an Application@ExternalID external identifier, or leave it empty.
# SavePerson2(Person2 person) → long (ID)
Also has a REST route — see REST.md.
Create-or-update a Person. If person.ID doesn't resolve to an existing record, the server first tries to find an existing match by last name + gender + last-name-prefix + (first name or initials) + birth date + email address (via ObjectLocator.FindPerson) before creating a new one — so sending an ID-less payload for what's actually an existing person can update that person instead of creating a duplicate, if the matching fields line up. person.VersionID (when > 0) is checked against the stored record's version and throws if the server's copy is newer (optimistic concurrency; pass 0 to skip the check). person.Owner/OwnerSub accept an Item whose ID is a contact ID (or, via Extra, a CM username) to resolve the owning user. person.ID may also be Application@ExternalID (or @ExternalCode appended) to also register/refresh a synchronizable-record link when creating a new person.
# SaveCompany2(Company2 company) → long (ID)
Also has a REST route — see REST.md.
Create-or-update a Company. Same ID/VersionID/Owner conventions as SavePerson2. If no existing record is found by ID, matches an existing company by RSIN number, DUNS number, Chamber of Commerce number, tax number, website, or name (via ObjectLocator.FindCompany) before creating a new one.
# SaveEmployee2(Employee2 employee) → long (ID)
Also has a REST route — see REST.md.
Create-or-update an Employee (the link between a Person and a Company). Requires employee.Person.ID and employee.Company.ID to already resolve to existing records when creating a new Employee — throws if either can't be found. If employee.LocationCode/LocationName or DepartmentCode/DepartmentName are given and don't match an existing location/department on the company, a new Department is auto-created (locations are matched only, not auto-created). The nested Company and Person objects are not written back to those records — only their IDs are read, to link the Employee. Use a separate SaveCompany2/SavePerson2 call to change company or person fields. The external identifier has to be unique across all employees — the server does not validate this, and a duplicate makes any later lookup by that identifier ambiguous. A common approach is the client ID plus an incrementing number.
# SaveAddress2(Address2 address) → long (ID)
Also has a REST route — see REST.md.
Create-or-update an Address. address.ContactID (ID or Application@ExternalID) identifies the owning Company/Person and is required when creating a new address. If no existing address is found by address.ID, the server tries to reuse an existing address on that contact matching the same city/street/street-number combination (skipping ones already linked to the same external application) before creating a new one. address.StreetLine is auto-parsed into Street/StreetNumber/StreetNumberExtension if those aren't supplied directly (format depends on the resolved country's street-line convention). LocationCode/LocationName auto-creates a Location on the company if no match is found (companies only — addresses on a Person don't get this location-matching logic). DefaultPostalAddress/DefaultVisitorAddress only take effect when sent as true — sending false does not clear an existing default flag. ContactManager does not validate the address against the destination country's addressing requirements. The external identifier has to be unique across all addresses — the server does not validate this, and a duplicate makes any later lookup by that identifier ambiguous. A common approach is the client ID combined with the address type.
# SaveMatter3(Matter3 matter) → long (ID)
Also has a REST route — see REST.md.
Create-or-update a Matter. matter.ClientID (ID or Application@ExternalID) or matter.ClientContactID (an Employee or CompanyDepartment ID) identifies the client — one of them must resolve to an existing Company or Person. matter.ActivityType defaults to "MATTER" if left blank (it is not a hard requirement — the reference implementation elsewhere describes distinct MATTER/MATTER_NOT/MATTER_ADV codes, but this method only enforces that some ActivityType lookup code resolves, defaulting to MATTER when none is given). matter.RTK/BTK/Originator accept an Item whose ID is a contact ID or, via Extra, a CM username, to resolve the responsible/originating user. matter.DepartmentCode/OfficeCode (optionally Application@ExternalID format) match against the internal companies' existing departments/locations — nothing is auto-created here, unlike SaveAddress2. When a new Matter is created, a MatterPayor attendee record for the client is also created automatically at 100%.
# SaveIdentification(Identification identification) → long (ID)
Also has a REST route — see REST.md.
Updates an existing Identification record (identification.ID must already resolve — this method does not create new ones). Throws if it doesn't.
# SaveAttendee(Attendee attendee) → long (ID)
Also has a REST route — see REST.md.
Create-or-update an activity/matter attendee link. Requires attendee.Contact and (for new attendees) attendee.Activity to resolve to existing records.
# SaveLookupItem(Item item) → long (ID)
Also has a REST route — see REST.md.
Create-or-update a lookup item of the type named in item.ObjectType (must be a valid, registered lookup type). item.Extra is the item's code, item.DisplayName its name.
# DeleteAttendee(id) → bool
Also has a REST route — see REST.md.
Removes an attendee link by ID. Returns false if id is empty or doesn't resolve.
# DeDuplicate(masterID, duplicateID) → string
Also has a REST route — see REST.md.
Merges duplicateID into masterID (Contact, Location, Address, or SubscriptionItem types only) and returns a human-readable result/error string rather than throwing. Returns an error message string (not an exception) if either ID doesn't resolve or the type isn't one of the supported ones.
# SaveActivity(id, activityType, contactID, subject, activityDate, ownerID, location, docUrl, attendeesIDs) → long (ID)
Also has a REST route — see REST.md.
Create-or-update a generic Activity (base type of Matter). contactID must resolve to an existing Contact. attendeesIDs (,-separated Contact IDs) fully replaces the attendee list on update — any existing attendee not in the list is removed, any new one is added.
# SaveActivityDescription(id, description, documentUrl) → bool
Also has a REST route — see REST.md.
Updates the Description/DocumentUrl of an existing Activity (id must resolve). Throws if it doesn't.
# SaveSynchronizableRecord(id, application, externalID, externalCode, markAsDirty) → long (ID)
Attaches or updates an external-system sync link on an existing record. Also has a REST route — see REST.md.
# SetValue(ID, field, value) → bool
Generic reflection-based single-field setter on any record type, by field name. Returns false if not authenticated or ID doesn't resolve.
# ID / reference conventions
Every Get*/Save* method's ID-like parameters accept:
- A ContactManager internal numeric ID, or
Application@ExternalID(external-system synchronization link), or, for a smaller number of ID parameters,Application@ExternalID@ExternalCode, or- For lookup-item types specifically, the lookup item's own code.
Owner/OwnerSub/RTK/BTK/Originator-style reference parameters accept an Item object; its ID is resolved the same way, and if that doesn't resolve, its Extra field is tried as a CM username instead.