A guide for developers

Integrate the Registered eDelivery service in under 3 days

Send documents securely and get indisputable confirmation of receipt and reading. The recipient authenticates with an electronic ID (eID) from Auðkenni before the document opens and becomes available to them. The web service integrates the deliveries directly into your own systems and interfaces. One REST API, one endpoint to get started, and a test environment you can try today.

30 minto your first delivery in the test environment
1–2 daystypical integration
< 3 daysfull implementation
In a hurry? (Or not a developer?)

The whole integration takes about 3 days: create a free trial account, email hallo@justikal.com to get API access, build and test in the test environment, then email again to finalize a contract and get access to the production environment. An experienced backend developer has usually sent the first document within half an hour of receiving the API key — many finish the entire integration in a single day. Skim the 7 integration steps below, then forward this page to your developer (if applicable).

The integration

The Registered eDelivery service is registered with and licensed by Byggðastofnun (the Icelandic Regional Development Institute) as a postal service. It can therefore be used, among other things, to send registered mail digitally in accordance with the relevant provisions of Icelandic law. Your system uploads a PDF document and tells Justikal who should receive it. Justikal notifies the recipient, who must authenticate with an electronic ID (eID) before the document opens. Every step — sent, opened, signed — is timestamped in an event log, and you can download a formal delivery receipt certificate for every successful delivery.

Your system uploads a PDF document Justikal eDelivery API Recipient authenticates 🔐 opens the document POST /edeliveries email / SMS status changes + event log + receipt certificate flow back to you
The whole service in one picture: you send, Justikal verifies and delivers, and the confirmation/certificate flows back to you.
New eDelivery document — the form in the Justikal dashboard
The same delivery, created manually in the web interface at demo.justikal.com — the API fields mirror this form 1:1 (pdfFile, recipientName, recipientPersonalCode, recipientEmail, message, services…). Everything your code does, you can first try here by hand.

The journey: 7 steps until everything is up and running

The green steps are yours; the dark steps are handled by Justikal. Only three of them involve writing code.

1

Create a free trial account You⏱ 10 min

Go to demo.justikal.com — Justikal's test environment — and sign up. You'll get a dashboard where you will later see every test delivery your code creates.

2

Request API access You⏱ 1 email

Email hallo@justikal.com and request API access. Include the email address you used when creating the trial account so the two can be linked.

✉️ To: hallo@justikal.com — Subject: Request for API access
“Hi — we would like API access to the eDelivery test environment. Our trial account is registered to email@yourcompany.com.”
3

Receive a test environment API key Justikal⏱ ~1 business day

You receive an API key (starting with jkt_) for the test environment at demoapi-is.justikal.com. Treat it like a password: keep it server-side only, ideally in a secret store — never in browser code, Git or logs.

4

Send your first test delivery Developer⏱ 30 min

One API call sends a document (detailed walkthrough below). Send it to yourself, open the delivery link, authenticate, and watch the status change in the dashboard.

5

Start the integration Developer⏱ 0.5–2 days

Pick your level: Minimal integration (send documents, track status by polling or in the dashboard), Full integration (webhooks, electronic signatures, automation) or Advanced integration (entirely under your own brand — your URL, your notifications, Justikal invisible). Start with the minimum — you can upgrade at any time.

6

Request a contract You⏱ 1 email

Does the integration work in the test environment? Email hallo@justikal.com again to finalize a service agreement and confirm which additional services you need (SMS, electronic signatures, delivery receipt certificates).

7

Integration review → Go live Justikal⏱ ~1 business day

Justikal reviews the integration and then issues a production environment API key. Switch the base URL from demoapi-is.justikal.com to api-is.justikal.com (the URLs are otherwise identical), run through the checklist — and you're all set.

🤖 Let AI do the work. The API follows the OpenAPI 3.0 standard, which means AI assistants such as Claude, ChatGPT or Copilot can read the entire machine-readable specification and write a working client in your programming language. Try: “Read the OpenAPI spec at https://demoapi-is.justikal.com/external/api/doc.json and write a typed client module for creating eDelivery deliveries, tracking status and downloading the delivery receipt certificate. Auth is a Bearer token.” — this alone often cuts the work down to a few hours.

Before you write any code

ItemValue
Test environment base URLhttps://demoapi-is.justikal.com
Production environment base URLhttps://api-is.justikal.com
Endpoint base path/external/api/v1/
AuthenticationAuthorization: Bearer jkt_your_key in every call
Interactive documentation/external/api/doc (Swagger UI) · /external/api/doc.json (OpenAPI spec)
Rate limits100 calls/minute per key (429 + Retry-After if exceeded)
Test PDFAny PDF, up to 300 MB — a one-page test document is enough

Your first delivery, step by step

What you do next

You make one HTTP call that uploads a PDF document and tells Justikal who should receive it. Justikal then emails the recipient a secure link on your behalf. The call is a plain multipart/form-data POST — the same format as a file upload form on any website — so every HTTP client supports it out of the box.

# Replace the key and use your own email address as the recipient,
# so YOU receive the delivery and see the whole experience.
curl -X POST "https://demoapi-is.justikal.com/external/api/v1/edeliveries" \
  -H "Authorization: Bearer jkt_your_key" \
  -F "pdfFile=@/path/to/document.pdf" \
  -F "recipientName=Jón Jónsson" \
  -F "recipientCountry=IS" \
  -F "recipientPersonalCode=1234567890" \
  -F "recipientEmail=you@yourcompany.com" \
  -F "emailSubject=A test delivery from us" \
  -F "message=Please review this document and confirm receipt."
The same call in Node.js
import fs from "node:fs";

const form = new FormData();
form.append("pdfFile", new Blob([fs.readFileSync("document.pdf")]), "document.pdf");
form.append("recipientName", "Jón Jónsson");
form.append("recipientCountry", "IS");
form.append("recipientPersonalCode", "1234567890");
form.append("recipientEmail", "you@yourcompany.com");
form.append("emailSubject", "A test delivery from us");
form.append("message", "Please review this document and confirm receipt.");

const res = await fetch(
  "https://demoapi-is.justikal.com/external/api/v1/edeliveries",
  { method: "POST",
    headers: { Authorization: `Bearer ${process.env.JUSTIKAL_TOKEN}` },
    body: form });
const delivery = await res.json();
The same call in Python
import os, requests

with open("document.pdf", "rb") as f:
    res = requests.post(
        "https://demoapi-is.justikal.com/external/api/v1/edeliveries",
        headers={"Authorization": f"Bearer {os.environ['JUSTIKAL_TOKEN']}"},
        files={"pdfFile": f},
        data={
            "recipientName": "Jón Jónsson",
            "recipientCountry": "IS",
            "recipientPersonalCode": "1234567890",
            "recipientEmail": "you@yourcompany.com",
            "emailSubject": "A test delivery from us",
            "message": "Please review this document and confirm receipt.",
        })
delivery = res.json()

What comes back

{
  "id": "550e8400-e29b-41d4-a716-446655440000",   ← store this — everything else refers to it
  "status": "pending",
  "delivery_url": "https://delivery.justikal.is/550e8400-...",
  "created_at": "2026-08-11T13:54:31+00:00"
}

Done — the recipient has an email. Store the id; every other endpoint (status, event log, PDF download, report, revocation) refers to it.

The most common first error: 400 with “At least one of recipient_email or recipient_phone must be provided” — you left out a recipient field, or set recipientEmail without emailSubject. The message in the error response always says exactly which field to fix.

What does the recipient see?

Worth understanding before you integrate — this is the experience your customers get, and exactly what you'll see when you send the first delivery to yourself.

📩 New secure document Open document Email (or SMS) from Justikal — or from your own system delivery.justikal.is 🔐 Confirm who you are Electronic ID (Auðkenni) Sign in with eID The identity is checked against the personal ID number you provided (hasAdditionalSecurity) delivery.justikal.is document.pdf ✓ Opened ✍ Sign Every action is timestamped 1 · Notification 2 · Authentication 3 · Document opens → status “delivered”

The delivery lifecycle

A delivery has one status at a time; timestamps and the event log hold the details. This is what your integration tracks:

pending delivered signed_at recorded (if a signature was requested) expired revoked recipient opens recipient signs deadline passes you revoke
Once status becomes delivered, the delivery receipt certificate is ready for download.

Choose your integration level

All three levels are fully valid in the production environment. The minimal level gets you live fastest, full integration automates everything, and advanced integration makes the experience entirely yours — your domain, your brand, your notifications.

Minimal integration · ~½ day

Send documents out, check status when it suits you

  • Send: POST /edeliveries — the call you saw above.
  • Track: query GET /edeliveries?status=pending a few times a day, or simply review the deliveries in the dashboard — no status polling in code required.
  • Fetch confirmation: once the status is delivered, download GET /edeliveries/{id}/deliveryReport.
  • Good for: back-office tools, low volume, going live this week.
Full integration · 1–2 days

Event-driven, branded as you, fully automated

  • Webhooks: set postbackUrl at creation — Justikal POSTs to you on every status change. No polling, instant reactions.
  • Your own notifications: set disableNotifications: true and send the delivery_url yourself from your own system.
  • Branding & language: companyBrandingUsed, senderNameHidden, lang (EN/IS).
  • Additional services: electronic signature, SMS channel, delivery receipt certificate via services.
  • Good for: customer-facing products, higher volume, automated operations.
Advanced integration · 2–3 days

Entirely under your own brand — the customer never leaves your environment

  • Your URL: display the delivery in an <iframe> on your own domain — e.g. docs.yourcompany.com/doc=73883.
  • Your notifications: email/SMS sent from your own address (notifications@yourcompany.com) — Justikal sends nothing.
  • Your brand: your logo on the delivery page, sender name hidden.
  • Justikal runs “headless”, doing its work behind the scenes.
  • Good for: banks, collection agencies, companies with their own service portals.
MINIMAL · you ask Your system Justikal API send poll for status …or skip code entirely and review statuses in the dashboard at demo.justikal.com 👀 Simple · few endpoints · ready in half a day FULL · Justikal tells you Your system + webhook endpoint Justikal API send + postbackUrl webhook on change ⚡ optional: your own branded email/SMS with delivery_url · signatures · seals · language Event-driven · branded as you · fully automated
Minimal = you ask for the status. Full = the statuses come to you.

Path A — Minimal integration

Step A1 · Send the document

Exactly the first call you made earlier. In production code the only change is reading the key from configuration and handling the response.

Step A2 · Check status (three equivalent ways)

Option 1 — no code: sign in to the dashboard and see every delivery, its status and event log. Perfectly adequate for low volume.

The eDeliveries dashboard with deliveries and statuses
The eDeliveries dashboard in the test environment — every delivery your code creates appears here with a live status. (Recipient details redacted.)

Option 2 — look up a single delivery:

curl "https://demoapi-is.justikal.com/external/api/v1/edeliveries/{id}" \
  -H "Authorization: Bearer jkt_your_key"

# → status, recipient and timestamps: created_at, delivered_at, signed_at, deadline_at

Option 3 — sweep everything pending (recommended for polling — one call instead of one per delivery, friendly to the 100 calls/min rate limit):

curl "https://demoapi-is.justikal.com/external/api/v1/edeliveries?status=pending&limit=50" \
  -H "Authorization: Bearer jkt_your_key"

Run this on a schedule — every 15–30 minutes is more than enough for most document workflows.

Step A3 · Download the delivery receipt certificate

# The formal delivery receipt certificate (returns 404 until delivery is complete)
curl ".../edeliveries/{id}/deliveryReport" -H "Authorization: Bearer jkt_..." -o receipt.pdf

# The document itself — use type=signed once signed_at is set
curl ".../edeliveries/{id}/pdf?type=signed" -H "Authorization: Bearer jkt_..." -o signed.pdf

Done. This is a fully valid minimal integration: one send call, one scheduled status sweep, one download when the job is finished.

Path B — Full integration

Step B1 · Set up a webhook endpoint

What you're doing: you give Justikal an HTTPS URL on your web server that gets POSTed to every time a delivery's status changes — your system reacts within seconds instead of waiting for the next poll.

// Express example — the pattern matters more than the framework
app.post("/webhooks/justikal", async (req, res) => {
  res.sendStatus(200);                       // 1. respond immediately
  const { id } = req.body;
  const delivery = await getDelivery(id);    // 2. re-fetch: the API is the source of truth
  await handleStatusChange(delivery);        // 3. idempotent: the same event twice = harmless
});
Two operating rules: re-fetch before you react (webhooks can arrive out of order or twice — treat them as “something changed”, not as the truth itself) and make your handling idempotent (processing the same notification twice must cause no harm).

Step B2 · Send with webhook + options

curl -X POST "https://demoapi-is.justikal.com/external/api/v1/edeliveries" \
  -H "Authorization: Bearer jkt_your_key" \
  -F "pdfFile=@contract.pdf" \
  -F "recipientName=Jón Jónsson" \
  -F "recipientCountry=IS" \
  -F "recipientPersonalCode=1234567890" \
  -F "recipientEmail=jon@example.com" \
  -F "emailSubject=Your contract is ready for signature" \
  -F "postbackUrl=https://yourapp.com/webhooks/justikal" \
  -F "expiresAt=2026-09-11" \
  -F "lang=IS" \
  -F "services[]=e_delivery_signature"

Step B3 · Optional: take over the recipient experience

FieldWhat it unlocks
disableNotifications: trueJustikal sends nothing — you send the delivery_url (or short_url) from your own branded system. Webhooks keep working.
companyBrandingUsed: trueYour logo on the delivery page.
senderNameHidden: trueOnly the company name is shown, not an individual's.
langNotification language (e.g. EN, IS).
servicese_delivery_signature (the recipient signs electronically), e_delivery_sms (SMS channel — requires recipientPhone + smsText), e_delivery_seal (electronically sealed delivery receipt certificate). Confirm with Justikal what your contract includes.
Want to go further — your own URL and an experience entirely under your own brand? The delivery page supports being embedded in an <iframe> on your own domain. That's the advanced integration below.

Step B4 · Lifecycle operations

# Revoke a delivery that hasn't been opened (only while status = pending)
curl -X POST ".../edeliveries/{id}/revoke" -H "Authorization: Bearer jkt_..."

# The full event log, event by event
curl ".../edeliveries/{id}/audit" -H "Authorization: Bearer jkt_..."

# GDPR: delete + anonymize personal data (permanent — use revoke for ordinary revocation)
curl -X DELETE ".../edeliveries/{id}" -H "Authorization: Bearer jkt_..."

Path C — Advanced integration (entirely under your own brand)

For companies that want nothing visible from Justikal in the customer journey: emails come from your address, the URL is yours, and the delivery flow happens on your own URL. Justikal runs “headless” behind the scenes.

Your system creates the delivery via the API ✉️ Your email / SMS notifications@yourcompany.com docs.yourcompany.com/doc=73883 Your branded document page <iframe src="delivery_url"> Justikal delivery view 🔐 eID → 📄 PDF → ✓ confirmation the only place Justikal is visible Justikal API · headless 1 · POST /edeliveries (disableNotifications: true) → returns delivery_url 2 · you notify 3 · your link 4 · iframe loads 5 · webhook on every status change → your system updates its own UI
The customer sees your email, your URL, your page. The Justikal view appears only inside the frame — where the legally required eID authentication takes place.

Step C1 · Create the delivery in headless mode

What you're doing: the same creation call as always, but with the flags that shift the customer experience over to you. This is exactly the combination used in bank/collection integrations running in production:

curl -X POST "https://demoapi-is.justikal.com/external/api/v1/edeliveries" \
  -H "Authorization: Bearer jkt_your_key" \
  -F "pdfFile=@Letter_884.pdf" \
  -F "recipientName=Jón Jónsson" \
  -F "recipientCountry=IS" \
  -F "recipientPersonalCode=1234567890" \
  -F "recipientEmail=jon@example.com" \
  -F "emailSubject=placeholder" \
  -F "expiresAt=2027-01-10" \
  -F "disableNotifications=true" \    # ★ Justikal sends no email or SMS
  -F "companyBrandingUsed=true" \     # your brand on the delivery view
  -F "senderNameHidden=true" \        # company name only, no individual
  -F "hasAdditionalSecurity=true" \
  -F "postbackUrl=https://ws.yourcompany.com/deliv-endpoint" \
  -F "lang=IS"

The response contains delivery_url — the starred fields (disableNotifications and postbackUrl) are the keys the whole pattern rests on.

Step C2 · Serve the delivery behind your own URL

What you're doing: you store the delivery_url against your own document number and display it in an iframe on a page you control. The recipient's browser never shows a Justikal URL:

<!-- https://docs.yourcompany.com/doc=73883 — your page, your brand -->
<iframe
  src="https://delivery.justikal.com/is/e9842df4-1b9a-4d43-bccc-a06d00245d6b?lang=is"
  style="width:100%; height:100vh; border:0"
  allow="publickey-credentials-get">
</iframe>

Append ?lang=is (or en) to the URL to control the language of the embedded view. eID authentication, document display and delivery confirmation all happen inside the frame.

Step C3 · Send your own notification

What you're doing: since Justikal stays silent, you email or text the recipient from your own system — your sender address, your template, your deadline in the copy — with a link to your page from step C2 (not to the raw delivery_url).

Step C4 · Let webhooks drive your UI

The postbackUrl webhook (same pattern as in path B) updates your own status displays and drives your next steps — reminders from your address, case updates in your core system, automatic download of the confirmation on delivery.

What “minimal Justikal” honestly means

TouchpointWhat the customer sees
Email / SMS notificationYou — your address, your template
Link & browser URLYou — docs.yourcompany.com
The document pageYou — your brand, with the delivery view embedded (company branding inside the frame, sender name hidden)
eID authenticationAuðkenni's standard flow inside the frame — required by law and familiar to users
Delivery receipt certificate (legal proof)A Justikal-sealed PDF
📄 What the delivery receipt certificate contains — the sealed PDF you download with GET /edeliveries/{id}/deliveryReport once the status is delivered: the document name and its SHA-256 hash, the recipient's name and personal ID number, the exact delivery timestamp (UTC), the authentication method and certificate issuer (Auðkenni), the sender's identity, the send time and deadline, and the delivery's unique number — all under a fully qualified electronic seal that makes the certificate tamper-proof.
Before you build path C: confirm with Justikal that iframe embedding and company branding are enabled for your account/contract — this is configured per contract.

When something goes wrong

All errors share the same JSON format — an error code plus a readable message that names the field or rule in question:

StatusMeaningFix
400A field is missing/invalid or a business rule was violated Read the message, fix the call, try again
401The key is missing or invalid Check the Authorization: Bearer jkt_... header
404Unknown ID — or the report isn't ready yet Verify the ID; for reports, wait for delivered
429Over 100 calls/minute Wait Retry-After seconds
500An error on Justikal's side Retry with exponential backoff; contact support if it persists

Go-live checklist