Payroll API
v1 Guides MK Get started

Payroll API

МПИН, е-ППД and ПДД-ГИ files

Important

Merot never submits МПИН, е-ППД or ПДД-ГИ filings to UJP. Every endpoint on this page returns a file; uploading it to UJP’s own e-МПИН / e-ППД portal is always something you or your accountant does yourselves.

МПИН (MPIN)

МПИН is UJP’s monthly filing of wages, contributions and personal income tax. Two file formats are accepted: the current XML format, and the legacy fixed-width TXT format UJP’s own client software also produces. Records are normally the corresponding fields straight out of a PayrollCalculation from POST /v1/calculations — the generator doesn’t recompute anything, it just formats what you give it.

Validate first

Run the same UJP-style checks the generators use, without producing a file — useful to surface issues in your own UI before offering a download:

curl -X POST "https://api.payroll.merot.com/v1/files/mpin/validate" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
const response = await fetch("https://api.payroll.merot.com/v1/files/mpin/validate", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({}),
});

const data = await response.json();
console.log(data);
import requests

response = requests.post(
    "https://api.payroll.merot.com/v1/files/mpin/validate",
    headers={
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
    },
    json={},
)

print(response.json())
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.payroll.merot.com/v1/files/mpin/validate', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_KEY',
        'Content-Type' => 'application/json',
    ],
    'json' => [],
]);

echo $response->getBody();
using System.Net.Http;
using System.Text;

using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.payroll.merot.com/v1/files/mpin/validate");
request.Headers.Add("Authorization", "Bearer YOUR_API_KEY");
var json = /* lang=json */ @"{}";
request.Content = new StringContent(json, Encoding.UTF8, "application/json");

using var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.payroll.merot.com/v1/files/mpin/validate"))
    .headers("Authorization", "Bearer YOUR_API_KEY", "Content-Type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString(/* JSON */ "{}"))
    .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());

Generate the XML

curl -X POST "https://api.payroll.merot.com/v1/files/mpin/xml" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
const response = await fetch("https://api.payroll.merot.com/v1/files/mpin/xml", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({}),
});

const data = await response.json();
console.log(data);
import requests

response = requests.post(
    "https://api.payroll.merot.com/v1/files/mpin/xml",
    headers={
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
    },
    json={},
)

print(response.json())
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.payroll.merot.com/v1/files/mpin/xml', [
    'headers' => [
        'Authorization' => 'Bearer YOUR_API_KEY',
        'Content-Type' => 'application/json',
    ],
    'json' => [],
]);

echo $response->getBody();
using System.Net.Http;
using System.Text;

using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.payroll.merot.com/v1/files/mpin/xml");
request.Headers.Add("Authorization", "Bearer YOUR_API_KEY");
var json = /* lang=json */ @"{}";
request.Content = new StringContent(json, Encoding.UTF8, "application/json");

using var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.payroll.merot.com/v1/files/mpin/xml"))
    .headers("Authorization", "Bearer YOUR_API_KEY", "Content-Type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString(/* JSON */ "{}"))
    .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());

By default, generation runs the same checks as POST /v1/files/mpin/validate and fails with 422 if any are blocking; pass force: true to generate anyway. TXT generation is byte-identical in structure but returned as text/plain; charset=utf-16le with a byte-order mark and CRLF line endings, exactly as UJP’s own client software expects — don’t re-encode it.

Combining several batches

If you run more than one payroll batch for the same legal entity and period (for example, a regular run plus a correction), POST /v1/files/mpin/txt/combined merges them into one filing, sorted by Cyrillic surname the way UJP expects.

ПДД-ГИ (contractor payments)

POST /v1/files/pddgi reports personal income tax withheld on contractor/honorarium payments made from a payroll batch — for the invoices you paid outside of regular employment.

е-ППД (standalone personal income tax)

POST /v1/files/epdd reports personal income tax on a payment made outside your regular payroll batch entirely. POST /v1/files/epdd/gross-from-net grosses up a net payment for a flat 10% personal income tax (the “+11.11%” rule accountants use) when you only know what the recipient is meant to receive.

Advanced: вид на обврска / filingVariant

Every МПИН batch defaults to вид на обврска 101 (regular). vidObvrska: "103" files a correction. A rarely-needed filingVariant option exists for two edge cases UJP has accepted in real filings — see the MpinBatch reference for exactly what each one changes; most integrations never need it.