ssm.ro Docs
API for Developers

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=M00212

Parameters

ParameterInTypeRequiredDescription
organizatiequerystringYesOrganization name
marcaquerystringYesBadge number — the unique identifying key for the employee

Responses

CodeDescription
200 OKEmployee 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 ErrorInternal 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/contacts

Creates 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

CodeDescription
200 OKThe contact was created — returns the Employee object
404 Not FoundOrganization not found
422 Unprocessable EntityInvalid data — JSON response with error details
500 Server ErrorInternal server error

Update existing contact

PATCH /v1/contacts/update

Updates 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

CodeDescription
200 OKThe contact was updated — returns the Employee object
204 No ContentNo changes made — the submitted data is identical to the existing data
404 Not FoundContact not found
422 Unprocessable EntityInvalid data — JSON response with error details
500 Server ErrorInternal 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)

SituationCodeExplanation
cnp already used by another employee422The CNP must be unique within the organization
cnp with incorrect format422Invalid CNP (valid format: 13 digits)
limba outside the accepted values422Only ro, en are allowed
dataIncepereActivitate with invalid dateignoredAn unparseable date is treated as empty (no error)
telefon too short (fewer than 9 digits)ignoredThe 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:

Contractmarcaemail
Primary contractM00212ion.popescu@exemplu.ro
Second contractM00478ion.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.

Organizationmarcaemail
demo-organizationM00212ion.popescu@exemplu.ro
demo-productieM00478ion.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

SituationRecommended solution
Both contracts belong to the same entityEmail aliases
The contracts belong to separately managed entities or divisionsDifferent organizations
The employee must have a single sign-in accountDifferent organizations
Both contracts must appear in the same organizational structureEmail 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.