Ketendo

Members

Creating and checking the internal Ketendo users your records reference.

UAT https://uat-ketendo-api.azurewebsites.net  ·  Production https://ketendo-api.azurewebsites.net  ·  Last updated 2026-09-21
Required must be sent, and valid, or the call fails Conditional required only in some situations (explained inline) Optional can be left out Auto-set the server fills this in - don't send it

Members

Members are the internal Ketendo users - your own staff, as opposed to the people and organisations you trade with. Save a member here before any record that has to reference one by id. The two read endpoints below let you check what already exists before you create anything.

Note the base path: SaveMember lives under /v1/integration/member/, and the two read endpoints under plain /v1/Member/ - not beside the records that reference them.
POST/v1/integration/member/SaveMemberCreate/update an internal user
FieldTypeRequired?Notes
ExternalIdstringRequiredYour unique key for this member, e.g. STAFF-4471
CompanyIdintRequiredWhich company this member belongs to - see the CompanyId note. Also accepted as Member.CompanyId; send it in either position
MemberobjectRequired
UserNamestringRequiredThe member's email address. This is the match key - see the callout below
FirstName, LastNamestringRequiredBoth are mandatory on the Ketendo member screen - please send them so records created via the API match what a user would have had to capture
OtherNamestringOptional
PhoneNumberstringRequiredSend it in international E.164 format, e.g. +27821234567. Ketendo validates numbers against the company's country, so E.164 is the safe form. A leading + is stripped before saving
RoleIdsarray of guidRequiredRoles to grant this member for CompanyId. Send exactly one role id - the Ketendo screen limits selection to one. On update, the roles for this company are replaced by what you send
ReportsToUserIdguid?OptionalInternal id of the member this person reports to - there is no email-based alternative for this field yet
UsesExternalAuthProviderboolOptionalDefault false. Set true if they sign in via SSO
SendInviteNotificationboolOptionalDefault false. We verified this only fires the first time a member is created - re-sending it for an existing member does nothing
DeletedboolOptionalDefault false. Set true to deactivate; their linked Employee record is deactivated with them
EmployeeobjectOptionalEmployee record to create/update alongside the member - MemberId and Deleted are stamped for you
userCommunicationChannelsarrayRequiredWhich channels this member is contacted on. Ketendo expects at least one entry per category - see the callout below for the exact values to send
↳ ↳ communicationChannelTypeIdintRequiredEmail = 1, SMS = 2, Platform (in-app) = 3. Email is what's normally selected, so send 1 unless you specifically need the others
↳ ↳ communicationChannelTypeCategoryIdintRequiredVerification = 1, In App = 2
↳ ↳ memberId-Auto-setDon't send - stamped for you once the member id is known
userCompanyAccessarrayOptionalCross-company role access; only saved when supplied
ExternalIdentifier-Auto-setDon't send - set from the ExternalId you sent
Members are matched on UserName (the email), not on your ExternalId. Sending a different UserName for the same ExternalId creates a second member rather than renaming the first, and the call then fails with "External member mapping mismatch" to stop us silently repointing your ExternalId at a different person. Treat UserName as fixed once a member exists.
On an update we verified only these are applied: PhoneNumber, the name fields, ReportsToUserId, RoleIds, Deleted, UsesExternalAuthProvider and ExternalIdentifier. UserName/email cannot be changed through this endpoint because it is the match key.
Communication channels - what to send. Email is the channel normally selected, and Ketendo expects one entry for each of the two categories. Copying the Ketendo screen's behaviour for an email-only member means sending exactly this:
"userCommunicationChannels": [
  { "communicationChannelTypeId": 1, "communicationChannelTypeCategoryId": 1 },
  { "communicationChannelTypeId": 1, "communicationChannelTypeCategoryId": 2 }
]
That is Email (1) for both Verification (1) and In App (2). Note there is no address field on these entries - the email used is the member's UserName, and the number is their PhoneNumber.
FirstName, LastName, PhoneNumber, RoleIds and userCommunicationChannels are marked required here because the Ketendo member screen enforces them. The API itself is more lenient and will technically accept a member without them, but doing so creates a record a Ketendo user could not have created - typically one with no role, so the person cannot be assigned work or sign in meaningfully. Please treat them as mandatory.
This endpoint does not set passwords. Use SendInviteNotification: true on first creation to have Ketendo email the member an invitation, or leave it false and onboard them separately.
GET/v1/Member/ValidateUsername/{username}Check whether a member already exists

Use this before SaveMember if you need to know whether an email is already taken - for example to decide between creating a member and just referencing the existing one.

EndpointUse for
GET /v1/Member/ValidateUsername/{username}Checking a brand-new member's email
GET /v1/Member/ValidateUsername/{memberId}/{username}Checking while editing an existing member - excludes that member from the check, so re-sending their own email doesn't report a clash
Read the result carefully - it is the reverse of what the name suggests. true means the username is already in use (so don't create it again); false means it is available.
This check matches on UserName across the whole platform, not just your company - the same person cannot exist twice under different companies with the same email. Only Authorization: Bearer <token> is needed; X-Source-System is not required.
POST/v1/Member/MembersList/search the members on a company

A second way to confirm what Ketendo already has - useful for reconciling your list of staff against ours, or for finding a member's internal id. It is a POST because the search/paging options go in the body.

FieldTypeRequired?Notes
IdentifierintRequiredThe CompanyId whose members you want
Identifier2guid?OptionalLeave this out. It applies role-level visibility filtering for a specific portal user and isn't useful for an integration
SearchCriteriastringOptionalFree-text search, e.g. an email or surname
PageNumber, PageSizeintOptionalStandard paging
SortBystringOptional
SortDirectionintOptional0 ascending, 1 descending
IncludeDeletedboolOptionalDefaults to true - send false if you only want active members
If you'd rather look a member up by the ExternalId you assigned, use GET /v1/EntityLookup/member/external/{externalId} from the Retrieve by ExternalId section instead - that's usually the simpler option once you've saved them at least once.
A lighter-weight variant returning fewer fields per member is available at POST /v1/Member/MembersList, taking the same body.