API v1

Dokumentacioni i API-se

Integroni timestamp me blockchain, nenshkrim elektronik eIDAS dhe vula dixhitale direkt ne sistemin tuaj.

Base URLhttps://www.doc.al/api/v1

Fillimi i shpejte

API-ja eshte e disponueshme per organizatat me plan Enterprise. Krijoni nje API key nga paneli juaj, pastaj beni thirrjen e pare.

  1. Hyni ne llogari dhe shkoni te Settings → API Keys
  2. Klikoni Krijo API Key, jepni nje emer, pastaj Krijo
  3. Ruajeni key-in menjehere. Ne databaze ruhet vetem hash-i i tij, ndaj nuk shfaqet perseri. Nese humbet, fshijeni dhe krijoni nje te ri.
Thirrja e pare
curl -X POST https://www.doc.al/api/v1/timestamp \
  -H "X-API-Key: docal_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{"hash":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}'

Autentikimi

Cdo kerkese kerkon API key-in tuaj. Pranohen te dyja format e meposhtme, zgjidhni ate qe ju pershtatet me mire.

Header
X-API-Key: docal_xxxxx

# ose

Authorization: Bearer docal_xxxxx

Key-i trashegon identitetin e perdoruesit dhe organizates qe e krijoi. Dokumentet e krijuara permes API-se i perkasin asaj organizate, dhe endpoint-et e vulave dhe te kuotes kerkojne qe key-i te jete i lidhur me nje organizate.

Limitet

Cdo key ka nje limit orar (parazgjedhur 100 kerkesa/ore), i zbatuar per key dhe jo per adrese IP. Kur tejkalohet, kthehet 429 me header-in Retry-After ne sekonda.

429 Too Many Requests
{
  "success": false,
  "error": "Limiti i kerkesave u tejkalua (100/ore). Provoni pas 1840 sekondash.",
  "timestamp": "2026-09-07T15:00:00.000Z"
}

Timestamp

Vulos nje hash SHA-256 ne zinxhirin e doc.al dhe e dergon automatikisht ne blockchain-in Polygon permes STAMLES. Pas konfirmimit, prova publikohet edhe ne IPFS.

POST/api/v1/timestampKrijo timestamp
POST/api/v1/timestamp/verifyVerifiko nje hash
POST /api/v1/timestamp
# Me hash te llogaritur nga ju
curl -X POST https://www.doc.al/api/v1/timestamp \
  -H "X-API-Key: docal_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{"hash":"<sha256 64 karaktere hex>"}'

# Ose duke ngarkuar skedarin - hash-i llogaritet nga serveri
curl -X POST https://www.doc.al/api/v1/timestamp \
  -H "X-API-Key: docal_xxxxx" \
  -F "[email protected]"
Pergjigja 201
{
  "success": true,
  "data": {
    "id": "cmms7s34n000001pmp31984ge",
    "sequenceNumber": 27,
    "fingerprint": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "sequentialFingerprint": "4a44dc15364204a80fe80e9039455cc1608281820fe2b24f1e5233ade6af1dd5",
    "type": "SUBMITTED_HASH",
    "serverTimestamp": "2026-09-07T15:00:00.000Z",
    "blockchain": {
      "network": "Polygon PoS",
      "system": "STAMLES Merkle Batching",
      "status": "QUEUED",
      "explorerUrl": "https://scan.stamles.eu/verify/9f86d081..."
    },
    "verifyUrl": "https://www.doc.al/verify/9f86d081...",
    "statusUrl": "https://www.doc.al/api/v1/timestamp/verify"
  }
}

Konfirmimi nuk eshte i menjehershem. STAMLES i grupon hash-et ne nje peme Merkle dhe shkruan vetem rrenjen ne blockchain nje here ne 24 ore. Statusi kalon QUEUED → BATCHED → CONFIRMED. Fushat polygonTxHash dhe ipfs.cid mbushen vetem pas konfirmimit. Vulosja vlen qe nga momenti i thirrjes, jo qe nga konfirmimi.

POST /api/v1/timestamp/verify
curl -X POST https://www.doc.al/api/v1/timestamp/verify \
  -H "X-API-Key: docal_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{"hash":"<sha256>"}'

# Pergjigja
{
  "success": true,
  "data": {
    "found": true,
    "sequenceNumber": 27,
    "serverTimestamp": "2026-09-07T15:00:00.000Z",
    "blockchain": {
      "network": "Polygon PoS",
      "status": "CONFIRMED",
      "txHash": "0x...",
      "blockNumber": 62841003,
      "batchId": "14",
      "explorerUrl": "https://scan.stamles.eu/verify/..."
    },
    "ipfs": {
      "cid": "QmXoyp...",
      "gatewayUrl": "https://ipfs.io/ipfs/QmXoyp..."
    }
  }
}

Nenshkrimi elektronik

Krijon nje kerkese nenshkrimi, dergon ftesat me email me brandin tuaj, dhe kthen nje link te sigurt nenshkrimi per secilin nenshkrues. Dokumenti perfundimtar merr nenshkrim PAdES, vulosje ne blockchain dhe prove ne IPFS.

POST/api/v1/signingKrijo kerkese nenshkrimi
GET/api/v1/signingLista e kerkesave
GET/api/v1/signing/:id/statusStatusi dhe progresi
POST /api/v1/signing
curl -X POST https://www.doc.al/api/v1/signing \
  -H "X-API-Key: docal_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "documentTitle": "Kontrata e Sherbimit",
    "documentUrl": "https://sistemi-im.com/dok.pdf",
    "documentHash": "<sha256 opsional>",
    "signers": [
      { "email": "[email protected]", "name": "Emri Mbiemri", "order": 0 },
      { "email": "[email protected]", "name": "Emri Tjeter", "order": 1 }
    ],
    "message": "Ju lutem nenshkruani kontraten",
    "expiresInDays": 30,
    "companyName": "Kompania Ime",
    "companyLogo": "https://sistemi-im.com/logo.png",
    "brandColor": "#dc2626",
    "callbackUrl": "https://sistemi-im.com/webhook/docal",
    "stamp": { "allPages": true, "barcode": "datamatrix" }
  }'
Pergjigja 201
{
  "success": true,
  "data": {
    "signingRequestId": "cmms7s34u000101pm...",
    "documentId": "cmms7s34u000201pm...",
    "status": "PENDING",
    "expiresAt": "2026-10-07T15:00:00.000Z",
    "signers": [
      {
        "id": "...",
        "name": "Emri Mbiemri",
        "email": "[email protected]",
        "signingUrl": "https://www.doc.al/sign/a1b2c3...",
        "status": "PENDING"
      }
    ],
    "tracking": {
      "statusUrl": "https://www.doc.al/api/v1/signing/<id>/status",
      "documentStatusUrl": "https://www.doc.al/api/v1/documents/<id>/status"
    },
    "webhook": {
      "callbackUrl": "https://sistemi-im.com/webhook/docal",
      "secret": "whsec_..."
    }
  }
}

Ruajeni webhook.secret - kthehet vetem njehere dhe ju duhet per te verifikuar cdo thirrje qe merrni.

GET /api/v1/signing/:id/status
{
  "success": true,
  "data": {
    "signingRequestId": "...",
    "status": "IN_PROGRESS",
    "expired": false,
    "progress": { "signed": 1, "total": 2, "pending": 1 },
    "document": { "id": "...", "title": "Kontrata", "status": "PARTIALLY_SIGNED", "fileHash": "..." },
    "signers": [
      { "signerName": "Emri Mbiemri", "status": "SIGNED", "viewedAt": "...", "signedAt": "..." },
      { "signerName": "Emri Tjeter", "status": "PENDING", "viewedAt": null, "signedAt": null }
    ],
    "timestamps": [ { "sequenceNumber": 28, "fingerprint": "...", "serverTimestamp": "..." } ]
  }
}

Vula me DataMatrix ne cdo faqe

Ne parazgjedhje, blloku i certifikimit vendoset nje here, ne fund te faqes se fundit. Me opsionin stamp ai perseritet ne fund te cdo faqeje, keshtu qe secila flete skanohet me vete dhe mban numrin e saj (Faqe 3/12).

Fusha stamp te POST /api/v1/signing
"stamp": {
  "allPages": true,
  "barcode": "datamatrix"
}

allPages

false (parazgjedhur) vendos bllokun vetem ne faqen e fundit. true e perserit ne cdo faqe.

barcode

both (parazgjedhur) · qr · datamatrix · none

QR-i con te faqja e verifikimit, ndersa DataMatrix-i permban hash-in e dokumentit - i lexueshem edhe nga skanera industriale qe nuk mbeshtesin QR. Blloku permban gjithashtu emrin e nenshkruesit, daten, referencen eIDAS dhe hash-in e shkurtuar.

Vulat dixhitale

Aplikon vulen e kompanise mbi nje dokument. Vula dhe dokumenti duhet t'i perkasin organizates suaj.

GET/api/v1/sealsLista e vulave
GET/api/v1/seals/:sealIdDetajet e nje vule
POST/api/v1/seals/applyApliko vulen
POST/api/v1/seals/:sealId/applyApliko vule specifike
POST/api/v1/seals/verifyVerifiko vulen
POST /api/v1/seals/apply
curl -X POST https://www.doc.al/api/v1/seals/apply \
  -H "X-API-Key: docal_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "documentId": "cmms7s34u000201pm...",
    "sealId": "cmms7s34u000301pm...",
    "position": { "page": 1, "x": 400, "y": 100 }
  }'

Dokumente dhe kuota

POST/api/v1/documentsNgarko PDF
GET/api/v1/documents/:id/statusStatus, nenshkrime, timestamps
GET/api/v1/quotaKuota e organizates
POST /api/v1/documents
curl -X POST https://www.doc.al/api/v1/documents \
  -H "X-API-Key: docal_xxxxx" \
  -F "[email protected]" \
  -F "title=Kontrata e Sherbimit"

Webhooks

Nese jepni callbackUrl kur krijoni kerkesen e nenshkrimit, doc.al ju njofton per cdo ndryshim. Dergesat riprovohen 3 here me backoff.

signature.signed

Sa here nje pale nenshkruan

signing_request.completed

Kur mbarojne te gjithe - permban hash-in perfundimtar

Header-at e dergeses
X-Docal-Event: signature.signed
X-Docal-Timestamp: 2026-09-07T15:00:00.000Z
X-Docal-Signature: sha256=<hmac>

Nenshkrimi eshte HMAC-SHA256 mbi <timestamp>.<body> me sekretin tuaj. Verifikojeni gjithmone para se te besoni permbajtjen.

Verifikimi ne Node.js
import crypto from "crypto";

// rawBody duhet te jete trupi i papërpunuar, jo JSON i ri-serializuar
export function verifyDocalWebhook(rawBody, headers, secret) {
  const timestamp = headers["x-docal-timestamp"];
  const received = headers["x-docal-signature"];
  if (!timestamp || !received) return false;

  const expected =
    "sha256=" +
    crypto.createHmac("sha256", secret)
      .update(timestamp + "." + rawBody)
      .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(received);
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

callbackUrl duhet te jete nje adrese publike http(s). Adresat lokale dhe rrjetet private refuzohen me 400.

Librari klienti

Nuk kerkohet asnje pako e jashtme - API-ja eshte REST me JSON. Me poshte gjeni nje klient te gatshem per gjuhet me te perdorura. Kopjojeni ne projektin tuaj dhe vendosni key-in ne nje variabel mjedisi.

Node.js - docal.js
const BASE = "https://www.doc.al/api/v1";

export class DocAl {
  constructor(apiKey = process.env.DOCAL_API_KEY) {
    if (!apiKey) throw new Error("DOCAL_API_KEY mungon");
    this.apiKey = apiKey;
  }

  async #request(path, { method = "GET", body } = {}) {
    const res = await fetch(BASE + path, {
      method,
      headers: {
        "X-API-Key": this.apiKey,
        ...(body ? { "Content-Type": "application/json" } : {}),
      },
      body: body ? JSON.stringify(body) : undefined,
    });

    const data = await res.json().catch(() => ({}));
    if (!res.ok) {
      const err = new Error(data.error || `HTTP ${res.status}`);
      err.status = res.status;
      throw err;
    }
    return data.data ?? data;
  }

  timestamp(hash) {
    return this.#request("/timestamp", { method: "POST", body: { hash } });
  }

  verifyTimestamp(hash) {
    return this.#request("/timestamp/verify", { method: "POST", body: { hash } });
  }

  createSigningRequest(payload) {
    return this.#request("/signing", { method: "POST", body: payload });
  }

  signingStatus(id) {
    return this.#request(`/signing/${id}/status`);
  }

  applySeal(documentId, sealId, position) {
    return this.#request("/seals/apply", {
      method: "POST",
      body: { documentId, sealId, position },
    });
  }

  quota() {
    return this.#request("/quota");
  }
}

// Perdorimi
import crypto from "crypto";

const docal = new DocAl();
const hash = crypto.createHash("sha256").update(fileBuffer).digest("hex");
const result = await docal.timestamp(hash);
console.log(result.sequenceNumber, result.blockchain.status);
PHP - DocAl.php
<?php

class DocAl
{
    private const BASE = 'https://www.doc.al/api/v1';
    private string $apiKey;

    public function __construct(?string $apiKey = null)
    {
        $this->apiKey = $apiKey ?? getenv('DOCAL_API_KEY') ?: '';
        if ($this->apiKey === '') {
            throw new RuntimeException('DOCAL_API_KEY mungon');
        }
    }

    private function request(string $path, string $method = 'GET', ?array $body = null): array
    {
        $ch = curl_init(self::BASE . $path);

        $headers = ['X-API-Key: ' . $this->apiKey];
        if ($body !== null) {
            $headers[] = 'Content-Type: application/json';
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
        }

        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CUSTOMREQUEST  => $method,
            CURLOPT_HTTPHEADER     => $headers,
            CURLOPT_TIMEOUT        => 30,
        ]);

        $raw    = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        $data = json_decode($raw, true) ?: [];
        if ($status >= 400) {
            throw new RuntimeException($data['error'] ?? "HTTP $status", $status);
        }

        return $data['data'] ?? $data;
    }

    public function timestamp(string $hash): array
    {
        return $this->request('/timestamp', 'POST', ['hash' => $hash]);
    }

    public function verifyTimestamp(string $hash): array
    {
        return $this->request('/timestamp/verify', 'POST', ['hash' => $hash]);
    }

    public function createSigningRequest(array $payload): array
    {
        return $this->request('/signing', 'POST', $payload);
    }

    public function signingStatus(string $id): array
    {
        return $this->request("/signing/$id/status");
    }
}

// Perdorimi
$docal = new DocAl();
$hash   = hash('sha256', file_get_contents('kontrata.pdf'));
$result = $docal->timestamp($hash);
echo $result['sequenceNumber'], ' ', $result['blockchain']['status'];
Python - docal.py
import hashlib
import os

import requests

BASE = "https://www.doc.al/api/v1"


class DocAlError(Exception):
    def __init__(self, message, status):
        super().__init__(message)
        self.status = status


class DocAl:
    def __init__(self, api_key: str | None = None):
        self.api_key = api_key or os.environ.get("DOCAL_API_KEY")
        if not self.api_key:
            raise RuntimeError("DOCAL_API_KEY mungon")
        self.session = requests.Session()
        self.session.headers["X-API-Key"] = self.api_key

    def _request(self, path: str, method: str = "GET", body: dict | None = None):
        res = self.session.request(method, BASE + path, json=body, timeout=30)
        data = res.json() if res.content else {}
        if not res.ok:
            raise DocAlError(data.get("error", f"HTTP {res.status_code}"), res.status_code)
        return data.get("data", data)

    def timestamp(self, file_hash: str):
        return self._request("/timestamp", "POST", {"hash": file_hash})

    def verify_timestamp(self, file_hash: str):
        return self._request("/timestamp/verify", "POST", {"hash": file_hash})

    def create_signing_request(self, payload: dict):
        return self._request("/signing", "POST", payload)

    def signing_status(self, request_id: str):
        return self._request(f"/signing/{request_id}/status")


# Perdorimi
docal = DocAl()

with open("kontrata.pdf", "rb") as f:
    file_hash = hashlib.sha256(f.read()).hexdigest()

result = docal.timestamp(file_hash)
print(result["sequenceNumber"], result["blockchain"]["status"])

Gabimet

Te gjitha gabimet kthehen ne te njejtin format, me kodin HTTP perkates.

kod
{
  "success": false,
  "error": "API key i pavlefshem ose i skaduar",
  "timestamp": "2026-09-07T15:00:00.000Z"
}
400Kerkese e pavlefshme - hash jo valid, fusha qe mungojne, callbackUrl jopublik
401API key mungon, eshte i pavlefshem ose ka skaduar
403Key-i nuk eshte i lidhur me nje organizate, ose burimi i perket dikujt tjeter
404Burimi nuk u gjet
429Limiti orar u tejkalua - shihni Retry-After
500Gabim ne server

Gati per te filluar?

API-ja eshte pjese e planit Enterprise. Na kontaktoni per akses.