Contacts
Endpoints for managing contacts (employees) in the ssm.ro platform: retrieval, creation, and modification.
The contacts endpoints allow you to sync employees from your HR system with the ssm.ro platform. A contact represents an employee enrolled in your organization.
Retrieve contact
GET /v1/contacts?organizatie={organizatie}&marca={marca}Returns the data of a contact based on the badge number (the unique identifying key) and the organization name. The badge number is passed as a query parameter, so badge numbers containing URL-unsafe characters are accepted too, as long as the value is encoded with encodeURIComponent().
Example request:
GET /v1/contacts?organizatie=demo-organization&marca=M00212Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
organizatie | query | string | Yes | Organization name |
marca | query | string | Yes | Badge number — the unique identifying key for the employee |
Responses
| Code | Description |
|---|---|
200 OK | Employee object |
404 Not Found | { "error": "Contactul nu a fost gasit" } — the badge number does not exist |
422 Unprocessable Entity | { "error": "Marca este obligatorie" } — the marca parameter is missing or empty |
500 Server Error | Internal server error |
Legacy route GET /v1/contacts/{id} — deprecated
Deprecated as of September 2026
The route that takes the badge number in the URL path (GET /v1/contacts/{id}) is
deprecated, because badge numbers containing URL-unsafe characters cannot be passed
correctly through a path segment. Use GET /v1/contacts?marca=, with the badge number
encoded via encodeURIComponent(). The legacy route still works, but is no longer maintained.
Create new contact
POST /v1/contactsCreates a new contact in the organization. If the badge number already exists, the operation will fail.
Request body
Content-Type: application/json — Employee object. The required fields are organizatie, marca, nume, departament, post and cor — see Required fields.
Example:
{
"nume": "Ion",
"prenume": "Popescu",
"email": "ion.popescu@exemplu.ro",
"departament": "Finanțe",
"codDepartament": "D163",
"cor": "Referent bancar",
"post": "Tesa",
"marca": "M00212",
"marcaSuperior": "M00132",
"adresa": "Str. Soarelui nr. 2",
"localitate": "Brașov",
"judet": "Brașov",
"dataNasterii": "1981-08-24",
"locatieFizica": "Office 101",
"status": "activ",
"organizatie": "demo-organization",
"echipaPSI": "Nu",
"telefon": "+40722333444",
"cnp": "1800101221144",
"limba": "ro",
"calificare": "Inginer",
"cetatenie": "Romana",
"nationalitate": "Romania",
"dataIncepereActivitate": "2020-01-15"
}Responses
| Code | Description |
|---|---|
200 OK | The contact was created — returns the Employee object |
404 Not Found | Organization not found |
422 Unprocessable Entity | Invalid data — JSON response with error details |
500 Server Error | Internal server error |
Update existing contact
PATCH /v1/contacts/updateUpdates the data of an existing contact, identified by the marca field. Only fields sent in the body with a non-empty value are updated — a field that is absent, null or "" is ignored.
Empty values do not clear existing data
A field sent as null or "" — just like one absent from the body — is ignored: the existing
value in the platform remains unchanged. To clear a piece of information you have to send a
non-empty string — see Clearing a piece of information below.
Request body
Content-Type: application/json — full Employee object. The required fields (organizatie, marca, nume, departament, post, cor) must be sent on PATCH too, even when their value does not change; organizatie and marca identify the contact.
Example:
{
"nume": "Ion",
"prenume": "Popescu",
"email": "ion.popescu@exemplu.ro",
"departament": "Finanțe",
"codDepartament": "D163",
"cor": "Referent bancar",
"post": "Tesa",
"marca": "M00212",
"marcaSuperior": "M00132",
"marcaSuperior2": "M0081",
"marcaInlocuitor": "M00301",
"adresa": "Str. Soarelui nr. 2",
"localitate": "Brașov",
"judet": "Brașov",
"dataNasterii": "1981-08-24",
"locatieFizica": "Office 101",
"status": "activ",
"organizatie": "demo-organization",
"echipaPSI": "Nu",
"telefon": "+40722333444",
"cnp": "1800101221144",
"limba": "ro",
"calificare": "Inginer",
"cetatenie": "Romana",
"nationalitate": "Romania",
"dataIncepereActivitate": "2020-01-15"
}Responses
| Code | Description |
|---|---|
200 OK | The contact was updated — returns the Employee object |
204 No Content | No changes made — the submitted data is identical to the existing data |
404 Not Found | Contact not found |
422 Unprocessable Entity | Invalid data — JSON response with error details |
500 Server Error | Internal server error |
Clearing a piece of information
Because empty values are ignored, a field cannot be emptied through the API. To clear an
existing piece of information, send any non-empty string in its place (e.g. "-", "n/a",
"no data"). The value is stored as-is and replaces the previous one — the platform does not
treat these values specially.
Does not work — the fields are ignored, the existing values remain unchanged:
{
"organizatie": "demo-organization",
"marca": "M00212",
"adresa": null,
"locatieFizica": "",
"calificare": null
}Works — the existing values are replaced:
{
"organizatie": "demo-organization",
"marca": "M00212",
"adresa": "-",
"locatieFizica": "-",
"calificare": "n/a"
}Free-text fields only
The placeholder works for free-text fields (adresa, localitate, judet, dataNasterii,
locatieFizica, calificare, cetatenie, nationalitate, cor, departament, post). Fields
with their own validation — cnp, telefon, dataIncepereActivitate, limba, status,
echipaPSI — only accept the documented values: a placeholder is either rejected with 422 or
ignored, per the validation table below.
Optional field behavior
organizatie, marca, nume, departament, post and cor are required on any write (POST and PATCH) — omitting any of them returns 422 / 400 (see Required fields). All other fields are optional. On PATCH, only the fields sent in the body are updated; a field that is absent or empty ("", null) is ignored — the existing value remains unchanged, so the API cannot empty a field (see Clearing a piece of information). In particular, a PATCH without status does not suspend the employee, and a POST without status creates the employee with activ status.
The department is identified by codDepartament. If you send the same codDepartament with a different departament value, the department name is updated (renamed) — a new department is not created. This way, a rename in HR propagates to the platform as long as the code stays the same.
New field validations (422 Unprocessable Entity)
| Situation | Code | Explanation |
|---|---|---|
cnp already used by another employee | 422 | The CNP must be unique within the organization |
cnp with incorrect format | 422 | Invalid CNP (valid format: 13 digits) |
limba outside the accepted values | 422 | Only ro, en are allowed |
dataIncepereActivitate with invalid date | ignored | An unparseable date is treated as empty (no error) |
telefon too short (fewer than 9 digits) | ignored | The number is normalized automatically; values that are too short are ignored (no error) |
Employees with multiple contracts
In the platform, an employee is identified by marca, while the access account is identified by
email. The email must be unique within an organization.
As a result, a person with multiple contracts in the same organization — that is, multiple
marca values — needs a separate contact for each contract, and those contacts cannot share
the same account.
The same email cannot be used twice in the same organization
If you send two contacts with different marca but the same email within the same
organization, the second contact cannot be created — the address is already taken. Every marca
in the organization needs a distinct email address.
There are two recommended solutions.
Solution 1 — email aliases (same mailbox, different addresses)
Each contract gets a distinct address, but all addresses deliver to the employee's same mailbox.
The simplest option is + sub-addressing, supported by Google Workspace, Microsoft 365 and most
mail servers:
| Contract | marca | email |
|---|---|---|
| Primary contract | M00212 | ion.popescu@exemplu.ro |
| Second contract | M00478 | ion.popescu+M00478@exemplu.ro |
{
"organizatie": "demo-organization",
"marca": "M00478",
"nume": "Ion",
"prenume": "Popescu",
"email": "ion.popescu+M00478@exemplu.ro",
"departament": "Producție",
"codDepartament": "D204",
"status": "activ"
}If the mail server does not support sub-addressing, you can use classic aliases defined on the mail
server (e.g. ion.popescu.2@exemplu.ro, forwarded to the same mailbox).
Implications:
- the employee receives notifications for both contracts in the same mailbox;
- sign-in happens separately, with each account's own address — these are distinct accounts, with separate documents and signatures;
- both contracts stay in the same organization and the same organizational structure.
Solution 2 — different organizations (a single account)
If the two contracts belong to different entities anyway, they can be split across different organizations within the same commercial account. The email only has to be unique inside an organization, so the same address can be used in each organization, and the employee accesses both contracts with a single account.
| Organization | marca | email |
|---|---|---|
demo-organization | M00212 | ion.popescu@exemplu.ro |
demo-productie | M00478 | ion.popescu@exemplu.ro |
At the API level, each contact is sent to its own organization through the organizatie field:
{
"organizatie": "demo-productie",
"marca": "M00478",
"nume": "Ion",
"prenume": "Popescu",
"email": "ion.popescu@exemplu.ro",
"status": "activ"
}Implications:
- the employee uses a single address and a single account, switching between organizations;
- the organizations are fully isolated — organizational structure, documents and reports are separate (see Glossary);
- this only makes sense if splitting across organizations is also justified operationally (separate legal entities, work sites or divisions).
How to choose
| Situation | Recommended solution |
|---|---|
| Both contracts belong to the same entity | Email aliases |
| The contracts belong to separately managed entities or divisions | Different organizations |
| The employee must have a single sign-in account | Different organizations |
| Both contracts must appear in the same organizational structure | Email aliases |
Syncing from HR
If your HR platform emits one record per contract, the synchronization must generate the distinct
address (alias) or route the record to the correct organization before the API call — marca
alone is not enough to avoid the email collision. See the
Node.js example.