KovaPDF API reference
The KovaPDF API gives your server the same three signature tools the website has: send a PDF to other people for signature and follow it with webhooks, sign a PDF with your own certificate, and verify the signatures in any PDF. It is a plain REST API, JSON over HTTPS. No SDK needed.
Looking for one error code? See every error code. What changed and when: the changelog. The rules for using the API: the API terms.
Quick start
- Sign in and create a key on Dashboard → API keys. Copy it straight away: it is shown once. Start with a free test key.
- Call the API with the key in the
Authorizationheader.
export KOVAPDF_KEY="kova_test_..."
curl https://api.kovapdf.com/api/v1/me \
-H "Authorization: Bearer $KOVAPDF_KEY"All endpoints live under https://api.kovapdf.com/api/v1. Requests with files are multipart/form-data; everything else is JSON. Responses are JSON, except the endpoints that return a PDF.
The three calls that take a file also take a JSON body with the file as base64, which suits tools that build flows without code: { "file": { "name": "a.pdf", "contentBase64": "JVBERi0..." } }. Use standard base64 with padding, with no data: prefix and no spaces or line breaks. The file goes through exactly the same checks and limits as an upload, and the call counts the same.
Authentication
Every request carries Authorization: Bearer <key>. A key acts as the account that made it. Requests you send are yours, signed-in cookies are ignored, and anything you could not do in the browser you cannot do with a key either.
- Test keys start with
kova_test_and live keys withkova_live_(see Test keys and live keys). Keys made before those two existed start withkova_. We store only a SHA-256 hash of each key, so a lost key cannot be shown again. Revoke it and create a new one. - Keep keys on your server. Never put one in a web page, a mobile app or a public repository.
- Revoking a key takes effect on the next request. You can have up to 10 active keys.
- When you make a key you choose what it may do (its scopes) and, if you like, a date it stops working: after 1 to 366 days, or never. We email you a week before a key expires. An expired key gets
401 expired_api_key. - In a Team or Business workspace, a key works on the workspace's signature requests. A member's own key stops the moment they leave the workspace. The Owner or an Admin can also make a key that belongs to the workspace itself: it acts for the workspace's Owner and keeps working when the person who made it leaves. The dashboard lists who made each one and when it was last used.
Test keys and live keys
This is a test key. It runs 10 tasks within 14 days. Every email it sends goes only to your own address, never to anyone else, and every file is marked TEST.
- Test key,
kova_test_. You make it yourself in your account once your email address is verified. You can hold one at a time. It runs 10 tasks within 14 days of your first test key, then it stops working: calls get401 test_key_used_upor401 test_key_expired, with arequestAccessUrl. - Every signing email a test request sends goes to the verified address on your account, whatever addresses the request names, with TEST in the subject. Every PDF a test key makes is stamped TEST on every page (for digital signing, before signing, so the signature covers the stamp). Test signature requests are deleted 7 days after they were made.
- Every answer to a test key carries the headers
KovaPDF-Mode: testandKovaPDF-Test-Tasks-Left: n, and every JSON object it gets back has"mode": "test". - Live key,
kova_live_. You make it in the same place, once we have turned API access on for your account. The email that says access is on never contains a key. Without API access, a call with a live key gets402 api_access_requiredwith arequestAccessUrl. Answers to a live key carryKovaPDF-Mode: live. - An account with API access also gets a number of test tasks every calendar month, which never count against its live limit. When they are used, test calls get
429 test_monthly_limituntil the 1st. - Older keys,
kova_. Keys made before test and live keys existed keep working for 30 days from the change, and their answers carry aKovaPDF-Key-Noticeheader with the date. After that they work only for an account with API access.
To ask for API access, use the request form.
HTTP/1.1 201 Created
KovaPDF-Mode: test
KovaPDF-Test-Tasks-Left: 9
{ "id": "4f0c…", "status": "IN_PROGRESS", …, "mode": "test" }Tasks
One task is one signature request made, one verification or one digital signing. Reading, listing, downloading and status calls are not tasks, and a call that fails is not counted.
| Endpoint | Task? |
|---|---|
POST /sign-requests | Yes, one task per request made |
POST /verify | Yes, one task per PDF checked |
POST /digital-sign | Yes, one task per PDF signed |
GET /me, GET /sign-requests, GET /sign-requests/:id, GET /sign-requests/:id/document, POST /sign-requests/:id/remind, POST /sign-requests/:id/cancel | No |
- A test task is set aside when the call starts, so ten calls at once can never run eleven tasks, and given back when the call fails.
- Live access has a monthly task limit or a pack of tasks. At the limit the answer is
429 monthly_limit(withRetry-Afteruntil the 1st of next month) or429 credits_used. Nothing is charged on top. Your account gets one email at 80 percent and one at 100 percent. - While a live call runs a task,
KovaPDF-Tasks-Leftsays how many tasks are left after it.
Idempotency-Key
Every POST takes an Idempotency-Key header: 1 to 255 visible characters, and a UUID is a good choice. Make a new one for each new request and store it with your own record, so a retry sends the same one.
- The first request with a key runs as usual and its answer is kept for 24 hours, for that API key.
- A repeat with the same key and the same body (the same fields and the same files) gets that first answer back with
Idempotency-Replayed: true. Nothing runs again: no second signature request, no second email, no second task. - The same key with a different body gets
422 idempotency_key_reused. While the first request is still running, a repeat gets409 idempotency_in_progresswithRetry-After: 2. A key that is empty, too long or has other characters gets400 invalid_idempotency_key. - An answer that a retry could change (5xx, 409, 429) is not kept, so a retry after it runs again.
- The kept answer can hold a signed PDF from
/digital-sign. It is deleted after 24 hours.
curl https://api.kovapdf.com/api/v1/sign-requests \
-H "Authorization: Bearer $KOVAPDF_KEY" \
-H "Idempotency-Key: 6f1c2a8e-3d4b-4f7a-9a51-2b8f0c9e7d13" \
-F "file=@agreement.pdf" -F "spec=<spec.json"
# The same call again, after a timeout:
HTTP/1.1 201 Created
Idempotency-Replayed: trueIP allow list
A key can be limited to the addresses your servers call from: up to 20 addresses or ranges, IPv4 or IPv6, such as 203.0.113.7, 203.0.113.0/24 or 2001:db8::/32. Set it when you make the key, or change it later in your account. Empty means any address. A range that covers the whole internet is refused, so nobody thinks they set a limit when they did not.
A call from an address that is not on the list gets 403 ip_not_allowed, with the address we saw in the message. Live access can also come with a list agreed for your account; then a live call must match that list too.
HTTP/1.1 403 Forbidden
{ "error": "This API key works only from the addresses on its allow list, and 198.51.100.4 is not on it. Change the list in your account.", "code": "ip_not_allowed" }Scopes
Give each key only what it needs. A call outside a key's scopes gets 403 insufficient_scope, with the scope it needed in required, the key's scopes in granted, and a WWW-Authenticate header that names it. GET /me works with any key. Keys made before scopes existed have all four.
| Scope | What it allows | Endpoints |
|---|---|---|
sign-requests:read | Read signature requests. List your signature requests, read one with its signers and audit trail, and download its documents. | GET /sign-requestsGET /sign-requests/:idGET /sign-requests/:id/document |
sign-requests:write | Send and manage signature requests. Send new signature requests, remind signers and cancel requests. | POST /sign-requestsPOST /sign-requests/:id/remindPOST /sign-requests/:id/cancel |
verify | Verify signatures. Check the digital signatures in a PDF and get the report. | POST /verify |
digital-sign | Sign with a certificate. Sign a PDF with your own .pfx or .p12 certificate. | POST /digital-sign |
tools | Work on files. Upload files and run tools on them: compress, merge, split, organize, convert, read text from scans, redact and convert Bangla or Hindi text. | POST /filesGET /files/:idPOST /jobsGET /jobs/:id |
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer realm="KovaPDF API", error="insufficient_scope", scope="sign-requests:write"
{
"error": "This API key cannot do this. It needs the "sign-requests:write" permission (Send and manage signature requests). Make a key with that permission in your dashboard.",
"code": "insufficient_scope",
"required": "sign-requests:write",
"granted": ["sign-requests:read"]
}Errors
Every error has a normal HTTP status and a JSON body with a human-readable error and a stable machine-readable code. Switch on code; the wording of error may change.
HTTP/1.1 401 Unauthorized
{ "error": "This API key has been revoked. Create a new one from your dashboard.", "code": "revoked_api_key" }| Status | Meaning | Example codes |
|---|---|---|
| 400 | The request is incomplete or not valid. | invalid_request (with issues), NO_FILE, BAD_PDF, invalid_base64, empty_file, SIGNER_EMAIL, FIELD_BOUNDS, WRONG_PASSWORD |
| 400 | The Idempotency-Key header is not valid. | invalid_idempotency_key |
| 401 | No key, an unknown key, a revoked key or an expired key, or a test key whose test has ended. | missing_api_key, invalid_api_key, revoked_api_key, expired_api_key, test_key_used_up (all its test tasks are used), test_key_expired (its 14 days are over) |
| 402 | A live call from an account without API access. | api_access_required (with requestAccessUrl) |
| 403 | The key or your account may not do this. | insufficient_scope (the key lacks the scope; see Scopes), two_factor_required (the workspace asks for two-step sign-in), workspace_inactive, EMAIL_NOT_VERIFIED, tool_blocked, account_inactive, FREE_SIGNER_LIMIT (more people than the Free plan allows on one request), key_paused (the key paused itself after a burst of tasks or too many bounced emails, or we paused it), ip_not_allowed (see IP allow list) |
| 404 | Not found, or not yours. | NOT_FOUND, NO_FINAL, not_found |
| 409 | The document's state does not allow it, or the same Idempotency-Key is still running. | CLOSED, SIGNATURE_FIELD_ALREADY_SIGNED, idempotency_in_progress |
| 413 | The file is too large. | file_too_large, FILE_TOO_LARGE |
| 422 | The file could not be processed, or an Idempotency-Key was reused for a different request. | invalid_pdf, encrypted_pdf, idempotency_key_reused |
| 429 | A limit was reached. Wait for Retry-After seconds. | rate_limited, daily_limit (the key's calls for today are used; it opens again at midnight UTC), limit_reached, DAILY_LIMIT, FREE_MONTHLY_LIMIT (the Free plan's signature requests for this calendar month are used; they come back on the 1st, or upgrade), FREE_RECIPIENT_DAILY (on the Free plan, one of the people has already had a lot of signature emails today; try again tomorrow, or upgrade), monthly_limit (your account's monthly tasks are used; nothing is charged on top), credits_used (your pack of tasks is used), test_monthly_limit (this month's test tasks of an account with API access are used) |
| 5xx | Our fault. Safe to retry with backoff. | service_unavailable |
Every code, with what it means and what to do: API error codes.
Limits
- 60 requests a minute per key. Every response carries
RateLimit-Limit,RateLimit-RemainingandRateLimit-Resetheaders; a 429 carriesRetry-After. - 30 signature requests sent an hour per key. Reminders count too.
- A daily cap per key, counted in UTC days: the lower of the cap you set on the key in the dashboard and your plan's cap for one key. Paid plans have higher caps than Free. The dashboard shows each key's calls per day and today's cap. When it is reached, calls get
429 daily_limituntil midnight UTC, and those refused calls do not count. - Every 429 has a
Retry-Afterheader in seconds and a plain sentence inerror. Wait that long, then try again. - Your account's own limits still apply. They are the same as in the browser: your plan's allowance, signature requests included, and any tool an administrator has switched off.
- Tasks. A test key runs 10 tasks within 14 days. Live access has a monthly task limit (
429 monthly_limit, withRetry-After) or a pack of tasks (429 credits_used). See Tasks. - A key that pauses itself. A key that runs far more tasks in 10 minutes than an integration normally does, or whose signing emails bounce too often, pauses itself and gets
403 key_paused. Write to us and we look at it with you. - Files up to 15 MB for signature requests. For signing and verifying, the same size and page limits as the website.
Request signatures
Send a PDF to one or more people. Each gets an email with a private link, signs in their browser, and everyone receives the finished document, sealed and with a certificate of completion. Your account needs a verified email address. The request goes out in your name.
POST/sign-requests
Multipart form with two parts: file (the PDF) and spec (a JSON string describing the request).
curl https://api.kovapdf.com/api/v1/sign-requests \
-H "Authorization: Bearer $KOVAPDF_KEY" \
-F "file=@agreement.pdf" \
-F 'spec={
"title": "Services Agreement",
"message": "Please sign by Friday.",
"sequential": true,
"expiresInDays": 14,
"reminderEveryDays": 3,
"signers": [
{ "name": "John Smith", "email": "john@example.com", "order": 1 },
{ "name": "Emily Carter", "email": "emily@example.com", "order": 2, "accessCode": "NW2026" }
],
"fields": [
{ "signer": 0, "kind": "SIGNATURE", "page": 1, "x": 72, "y": 90, "width": 180, "height": 48 },
{ "signer": 0, "kind": "DATE", "page": 1, "x": 72, "y": 50, "width": 180, "height": 16 },
{ "signer": 1, "kind": "SIGNATURE", "page": 1, "x": 340, "y": 90, "width": 180, "height": 48 }
]
}'| spec field | Type | Notes |
|---|---|---|
title | string | Required, up to 150 characters. The email subject. |
message | string | Optional note in the invitation, up to 2,000 characters. |
sequential | boolean | Required. true: one after another by order. false: everyone at once. |
expiresInDays | integer | Optional, 1 to 60 days. Without it, the usual expiry applies. |
reminderEveryDays | integer | Optional automatic reminders, every 1 to 14 days. |
signers[] | array | name, email, order (from 1), optional accessCode (4 to 12 letters or digits, sent to them separately by you), optional role: SIGNER (default), APPROVER, WITNESS (with witnessFor: the index of the signer they witness) or CC. |
fields[] | array | signer (index into signers), kind (SIGNATURE, INITIALS, STAMP, NAME, EMAIL, COMPANY, TITLE, DATE, DATE_PICK, TEXT, NUMBER, CHECKBOX, DROPDOWN, RADIO, ATTACHMENT, FORMULA, NOTE, APPROVAL), page (from 1), x, y, width, height in PDF points from the page's lower-left corner, optional required and label. EMAIL is filled in with the recipient's own address. DATE is the day they sign; DATE_PICK is a date they choose. NOTE shows words to the recipient and has nothing to fill in. APPROVAL is the Approve or Decline box, and only an APPROVER can have it; an approver has only APPROVAL and NOTE fields. Every signer and witness needs at least one SIGNATURE field. Optional prefill fills a TEXT, NUMBER, DATE_PICK, COMPANY or TITLE field in advance. Optional options: choices and defaultValue for a dropdown; group and choice for each radio button (two or more per group); validate (number, email, date with dateFormat) and maxLength for text; dateFormat for a DATE_PICK; note for a NOTE; formula such as [Qty] * [Price] over the same signer's number fields, with decimals; appendToPdf for an attachment; and on any field but a signature or approval showIf: { field, value }, where field is the index of the same signer's checkbox ("true"/"false"), dropdown, radio button or text field. |
Returns 201 with the request, the same object as GET /sign-requests/:id. Each signer in it has a link: their private signing link, if you want to show it in your own app as well as by email.
GET/sign-requests
Your requests, newest first (up to 200), with each signer's status.
curl https://api.kovapdf.com/api/v1/sign-requests -H "Authorization: Bearer $KOVAPDF_KEY"GET/sign-requests/:id
One request: status (IN_PROGRESS, COMPLETED, DECLINED, CANCELLED, EXPIRED), its signers with their status and times, and the full audit trail in events.
{
"id": "4f0c…",
"title": "Services Agreement",
"status": "IN_PROGRESS",
"sequential": true,
"expiresAt": "2026-10-06T09:12:00.000Z",
"hasFinal": false,
"signers": [
{ "id": "aa57…", "name": "John Smith", "email": "john@example.com", "order": 1,
"status": "SIGNED", "signedAt": "2026-09-22T09:30:11.000Z", "link": "https://…/sign/…" },
{ "id": "c1d2…", "name": "Emily Carter", "email": "emily@example.com", "order": 2,
"status": "SENT", "isTurn": true, "link": "https://…/sign/…" }
],
"events": [ { "at": "…", "type": "CREATED", "text": "Created and sent by the sender (2 signer(s), in order)" } ]
}POST/sign-requests/:id/remind
Emails the people whose turn it is again. Optional JSON body { "signerId": "…" } to remind one person. Each person can be reminded once every 12 hours; anyone reminded more recently is listed in skipped.
curl -X POST https://api.kovapdf.com/api/v1/sign-requests/$ID/remind \
-H "Authorization: Bearer $KOVAPDF_KEY" -H "Content-Type: application/json" -d '{}'
# { "sent": 1, "skipped": [] }POST/sign-requests/:id/cancel
Closes a request that is still waiting for signatures. The signing links stop working.
curl -X POST https://api.kovapdf.com/api/v1/sign-requests/$ID/cancel -H "Authorization: Bearer $KOVAPDF_KEY"GET/sign-requests/:id/document?version=original|current|final
Downloads a PDF. original (default) is the file you uploaded; current shows the signatures collected so far; final is the sealed, signed document with its certificate of completion, and exists once everyone has signed (404 NO_FINAL before that).
curl -o signed.pdf "https://api.kovapdf.com/api/v1/sign-requests/$ID/document?version=final" \
-H "Authorization: Bearer $KOVAPDF_KEY"Digital signature
POST/digital-sign
Signs a PDF with your own certificate (PAdES) and returns the signed PDF. Multipart fields: file (the PDF), certificate (a .pfx/.p12), password, and any options below. The certificate and password are used once, in memory: they are never stored and never logged.
curl https://api.kovapdf.com/api/v1/digital-sign \
-H "Authorization: Bearer $KOVAPDF_KEY" \
-F "file=@contract.pdf" \
-F "certificate=@me.pfx" \
-F "password=$PFX_PASSWORD" \
-F "reason=Approved" -F "location=London" \
-F "placement=bottom-right" \
-o contract-signed.pdf| Option | Default | Notes |
|---|---|---|
reason, location | None | Shown in the signature and its stamp. |
hashAlgorithm | SHA-256 | or SHA-512. |
visible | true | false for a signature with no stamp on the page. |
placement | bottom-right | bottom-left, top-right, top-left; on the last page unless page is set. |
page, rect | None | Exact position: rect is a JSON array [x1,y1,x2,y2] in points from the lower-left of the page as displayed. Overrides placement. |
fieldName | None | Sign into an existing empty signature field. |
signatureImage | None | A PNG, JPEG or WebP file part to draw in the stamp. |
certify | none | no-changes, form-filling, form-filling-and-comments: a certification signature (first signature only). |
lockDocument | false | Lock the form fields after signing. |
timestamp | true | Add a trusted timestamp. |
ltv, archiveTimestamp | false | Long-term validation data (needs timestamp), and an archive timestamp (needs ltv). |
showName, showDate, showReason, showLocation, showLabels | true | What the visible stamp shows. |
The response is the PDF. Headers describe what it verifiably has: X-Signature-Profile (e.g. PAdES-B-T), X-Signature-Timestamp, X-Signature-LTV, X-Signature-Hash, X-Signature-Certify, X-Signature-Signer (URL-encoded) and X-Signature-Status (base64 JSON). A wrong password is 400 WRONG_PASSWORD; an expired certificate is 400 CERTIFICATE_EXPIRED.
Verify signatures
POST/verify
Checks every signature in a PDF (integrity, certificate, chain, revocation, timestamp and whether the document changed after signing) and returns the same report the Verify tool shows.
curl https://api.kovapdf.com/api/v1/verify \
-H "Authorization: Bearer $KOVAPDF_KEY" \
-F "file=@signed.pdf"{
"fileSha256": "9c1e…",
"pageCount": 3,
"signatures": [
{
"verdict": "UNCHANGED",
"status": { "signature": "VALID", "certificate": "VALID", "certificateChain": "VALID",
"revocation": "NOT_REVOKED", "trust": "TRUSTED", "timestamp": "VALID",
"ltv": "PRESENT_NOT_VERIFIED", "pades": "B-LT_STRUCTURALLY_CONFORMANT" },
…
}
],
…
}verdict per signature is one of UNCHANGED, ALLOWED_CHANGES, CHANGED, REVOKED or BROKEN. A PDF with no signatures returns an empty signatures array.
Webhooks
Add an HTTPS endpoint on Dashboard → API keys and we will POST a JSON event to it whenever one of your signature requests changes, including requests sent from the website. Choose which events to receive, or leave them all on.
| Event | When |
|---|---|
request.sent | A request was created and the first invitations went out. |
signer.viewed | A signer opened the document for the first time. |
signer.signed | A signer (or witness) signed. |
signer.approved | An approver approved. |
signer.declined | A signer declined; the request is closed. |
signer.reassigned | A signer handed their turn to someone else. |
request.completed | Everyone signed; the final document is ready. |
request.expired | The expiry date passed before everyone signed. |
request.cancelled | You cancelled the request. |
ping | You pressed “Send test” on the dashboard. |
POST /your/webhook HTTP/1.1
Content-Type: application/json
KovaPDF-Event: signer.signed
KovaPDF-Delivery: 7b9eebb3-91bb-4feb-991a-63685a471937
KovaPDF-Signature: t=1790000000,v1=5f2c…
{
"id": "7b9eebb3-91bb-4feb-991a-63685a471937",
"type": "signer.signed",
"createdAt": "2026-09-22T09:30:11.482Z",
"data": {
"request": { "id": "4f0c…", "title": "Services Agreement", "status": "IN_PROGRESS",
"createdAt": "…", "completedAt": null, "expiresAt": "…" },
"signer": { "id": "aa57…", "name": "John Smith", "email": "john@example.com", "role": "SIGNER", "status": "SIGNED" }
}
}Delivery and retries
- Answer with any
2xxwithin 10 seconds. Anything else (an error status, a redirect, a timeout) counts as a failure. - Failures are retried after 10 s, 1 min, 5 min, 30 min, 2 h and 6 h (7 attempts in all). The last 20 deliveries per endpoint, with their status, are on the dashboard, where you can also resend one.
- Delivery is at-least-once and not strictly ordered. Use the
id(also inKovaPDF-Delivery) to ignore duplicates, and fetch the request when you need its latest state.
Verifying the signature
Every delivery is signed with your endpoint's signing secret (whsec_…, shown on the dashboard).KovaPDF-Signature is t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of <t>.<raw request body>. Compute it over the raw body bytes (before any JSON parsing), compare in constant time, and reject timestamps more than five minutes old.
Node.js
import crypto from "node:crypto";
import express from "express";
const SECRET = process.env.KOVAPDF_WEBHOOK_SECRET; // whsec_...
function verifyKovaSignature(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds || !parts.v1) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
const app = express();
app.post("/kovapdf/webhook", express.raw({ type: "application/json" }), (req, res) => {
const raw = req.body.toString("utf8");
if (!verifyKovaSignature(raw, req.get("KovaPDF-Signature") ?? "", SECRET)) return res.sendStatus(400);
const event = JSON.parse(raw);
// ...handle event.type, deduplicate on event.id...
res.sendStatus(200);
});
app.listen(3000);Python
import hashlib, hmac, os, time
from flask import Flask, request, abort
SECRET = os.environ["KOVAPDF_WEBHOOK_SECRET"] # whsec_...
def verify_kova_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
try:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
app = Flask(__name__)
@app.post("/kovapdf/webhook")
def kovapdf_webhook():
raw = request.get_data() # raw bytes, before parsing
if not verify_kova_signature(raw, request.headers.get("KovaPDF-Signature", ""), SECRET):
abort(400)
event = request.get_json()
# ...handle event["type"], deduplicate on event["id"]...
return "", 200Rotating a secret on the dashboard takes effect immediately: deliveries from then on (including retries of older events) are signed with the new secret.
OpenAPI file
The whole API is described in one OpenAPI 3.1 file: every endpoint, the key header, the scope each endpoint needs, the errors, the limits and examples. Import it into an API client, or generate a client in the language you use.
- On this website: kovapdf.com/developers/openapi.json
- A Postman collection with one request for every endpoint, the variables
baseUrlandapiKey, and a freshIdempotency-Keyon every POST: kovapdf.com/developers/kovapdf.postman_collection.json - From the API itself, no key needed:
https://api.kovapdf.com/api/v1/openapi.json
curl https://api.kovapdf.com/api/v1/openapi.json -o kovapdf-openapi.json