Skip to content

Developers v1 contract in design

Your first record, in ten minutes.

An API with an OpenAPI contract, TypeScript and Python SDKs, a CLI, a local sandbox with a simulated station and documentation that AI can read too. From test key to sealed, verified record, without emailing anyone.

The first time you open the dashboard

Seven steps. Ten minutes.

An interactive guide that saves your progress. Skip it and pick it up whenever you like.

  1. 1

    Account and 2FA

    Sign in and turn on the second factor. It is mandatory for owners and admins.

  2. 2

    Your test key

    The key wizard, in three steps. Test keys do not use quota.

  3. 3

    Seal your first record without code

    The browser builds the request with a sample capture or a photo of yours and sends it. You watch the record move, live.

  4. 4

    Verify it

    Open its public page and the file checker. We explain what the fingerprint, the time and the signature are.

  5. 5

    Check the log

    The call you just made is already in your account’s API log.

  6. 6

    Receive a webhook

    In the dashboard’s test inbox or at your own URL. With manual resend.

  7. 7

    Choose your path

    Download the SDK, connect an AI, start the local sandbox or enrol a PackNSign station with the simulator.

The same guide exists as text in the documentation and as a permanent playground in the dashboard.

Seal

One call. One sealed record.

Create the record from your WMS, your shop or your claims platform. It answers at once and tells you by webhook when it is sealed.

curl https://api.knowyourparcel.com/v1/records \
  -H "Authorization: Bearer $KYP_API_KEY" \
  -H "KYP-Version: 2026-10-16" \
  -H "Idempotency-Key: 6f1c2a90-8d1e-4b7a-9c3f-2b51e0d4a713" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "outbound",
    "references": [{ "kind": "order", "value": "ORD-88213" }],
    "payload": { "weight_kg": 2.34, "dims_cm": [32, 24.5, 18] },
    "assets": [{ "role": "top", "upload": "upl_01J…" }]
  }'
import { KYP } from '@knowyourparcel/sdk';

const kyp = new KYP({ apiKey: process.env.KYP_API_KEY });

const record = await kyp.records.create({
  kind: 'outbound',
  references: [{ kind: 'order', value: 'ORD-88213' }],
  payload: { weight_kg: 2.34, dims_cm: [32, 24.5, 18] },
  assets: [{ role: 'top', file: './top.jpg' }],
});

const sealed = await kyp.records.waitFor(record.id, 'sealed');
console.log(sealed.verify_url);
import os
from knowyourparcel import KYP

kyp = KYP(api_key=os.environ["KYP_API_KEY"])

record = kyp.records.create(
    kind="outbound",
    references=[{"kind": "order", "value": "ORD-88213"}],
    payload={"weight_kg": 2.34, "dims_cm": [32, 24.5, 18]},
    assets=[{"role": "top", "file": "top.jpg"}],
)

sealed = kyp.records.wait_for(record.id, "sealed")
print(sealed.verify_url)
Response · 202
{
  "id": "KYP-2026-000123",
  "status": "sealing",
  "kind": "outbound",
  "received_at": "2026-10-16T09:41:18.204Z",
  "payload_hash": "sha256:…a91f",
  "verify_url": "https://verify.knowyourparcel.com/v/KYP-2026-000123",
  "request_id": "req_01J…"
}

And anyone can check it.

Verification is public and needs no key. With the Python SDK you can check a record offline: fingerprint, Ed25519 signature and RFC 3161 timestamp, without asking us.

curl https://api.knowyourparcel.com/v1/verify/KYP-2026-000123
const result = await kyp.verify('KYP-2026-000123');

result.intact;              // true
result.checks.timestamp;    // 'valid'
result.capture_trust;       // 'attested'
from knowyourparcel.offline import verify_record

result = verify_record("KYP-2026-000123.json")

result.fingerprint   # "match"
result.signature     # "valid"   Ed25519
result.timestamp     # "valid"   RFC 3161

Keys and tokens

Keys in three steps.

  1. 01

    Name and environment

    Test or live. Test keys point to a hosted test environment, with isolated data and a simulated seal marked as such.

  2. 02

    Scopes, in plain language

    Ready-made templates: read only, warehouse integration, public verification, device. Or scope by scope.

  3. 03

    Restrictions

    Allowed IPs, expiry and a per-minute limit.

  • Rotation with overlap: the old key keeps working for 24 h.
  • Instant revocation and “last used”, with the IP.
  • An alert if a live key is used from a new IP.
  • CLI with kyp login: it opens the browser, no keys pasted into the terminal.
  • Stations enrol with a one-time code and QR.

API v1

Everything is a call.

REST and JSON, versioned by path and by date. Bearer authentication, idempotency on every POST that creates, and a log of every call that you can see too. Open an endpoint to see the shape of the response.

Records

POST/v1/recordsCreates a record from a capture and seals it. Returns 202 and notifies by webhook once sealed.
Request
{
  "kind": "outbound",
  "references": [
    {
      "kind": "order",
      "value": "ORD-88213"
    },
    {
      "kind": "tracking",
      "value": "1Z…"
    }
  ],
  "payload": {
    "weight_kg": 2.34,
    "dims_cm": [
      32,
      24.5,
      18
    ]
  },
  "assets": [
    {
      "role": "top",
      "upload": "upl_01J…"
    },
    {
      "role": "perspective",
      "upload": "upl_01J…"
    }
  ]
}
Response
{
  "id": "KYP-2026-000123",
  "status": "sealing",
  "kind": "outbound",
  "received_at": "2026-10-16T09:41:18.204Z",
  "payload_hash": "sha256:…a91f",
  "verify_url": "https://verify.knowyourparcel.com/v/KYP-2026-000123",
  "request_id": "req_01J…"
}
GET/v1/records/{id}The full record: size, weight, references, device signature, capture trust level and seal.
Response
{
  "id": "KYP-2026-000123",
  "status": "sealed",
  "kind": "outbound",
  "references": [
    {
      "kind": "order",
      "value": "ORD-88213"
    }
  ],
  "payload": {
    "weight_kg": 2.34,
    "dims_cm": [
      32,
      24.5,
      18
    ],
    "label": {
      "tracking": "1Z…"
    }
  },
  "device": {
    "id": "dev_01J…",
    "model": "PackNSign 60",
    "signature": "valid"
  },
  "capture_trust": "attested",
  "seal": {
    "payload_hash": "sha256:…a91f",
    "timestamp": "RFC 3161",
    "sealed_at": "2026-10-16T09:41:19Z",
    "prev_record_hash": "sha256:…07c4"
  },
  "assets": [
    {
      "role": "top",
      "sha256": "…3b9e"
    },
    {
      "role": "perspective",
      "sha256": "…c1d0"
    }
  ]
}
GET/v1/records?reference=ORD-88213Searches by order, tracking, SKU, customer, station or dates. Cursor pagination.
Response
{
  "data": [
    {
      "id": "KYP-2026-000123",
      "kind": "outbound",
      "sealed_at": "2026-10-16T09:41:19Z"
    },
    {
      "id": "KYP-2026-000481",
      "kind": "return",
      "sealed_at": "2026-10-28T15:02:44Z"
    }
  ],
  "next": "cur_01J…"
}
GET/v1/records/{id}/assets/{assetId}One image, video or point cloud, through a short-lived signed URL.
Response
{
  "role": "top",
  "mime": "image/jpeg",
  "sha256": "…3b9e",
  "url": "https://api.knowyourparcel.com/v1/files/…?sig=…",
  "expires_in": 300
}
POST/v1/uploadsSigned URL to upload large files straight to storage.
Request
{
  "filename": "top.jpg",
  "mime": "image/jpeg",
  "bytes": 4182231,
  "sha256": "…3b9e"
}
Response
{
  "id": "upl_01J…",
  "put_url": "https://api.knowyourparcel.com/v1/uploads/upl_01J…?sig=…",
  "expires_in": 900
}

Verification · public, no key

GET/v1/verify/{ref}Checks fingerprint, timestamp, device signature and chain.
Response
{
  "id": "KYP-2026-000123",
  "intact": true,
  "sealed_at": "2026-10-16T09:41:19Z",
  "checks": {
    "fingerprint": "match",
    "timestamp": "valid",
    "device_signature": "valid",
    "chain": "linked",
    "anchor": "2026-10-16"
  },
  "capture_trust": "attested"
}
POST/v1/verify/fileUpload a PDF, JSON or image and learn whether it is an intact record.
Request
multipart/form-data   file=@KYP-2026-000123.pdf
Response
{
  "is_kyp_record": true,
  "id": "KYP-2026-000123",
  "intact": false,
  "reason": "fingerprint_mismatch"
}
GET/v1/anchors/{date}The daily anchor: the platform-wide chain root, sealed and published.
Response
{
  "date": "2026-10-16",
  "merkle_root": "sha256:…e2a7",
  "records": "n",
  "timestamp": {
    "standard": "RFC 3161",
    "token": "MIIG…"
  },
  "published": "https://verify.knowyourparcel.com/anchors/2026-10-16"
}

Analysis

POST/v1/compareSets two records side by side. Returns differences, not verdicts.
Request
{
  "a": "KYP-2026-000123",
  "b": "KYP-2026-000481"
}
Response
{
  "weight_kg": {
    "a": 2.34,
    "b": 0.41,
    "delta": -1.93
  },
  "dims_cm": {
    "a": [
      32,
      24.5,
      18
    ],
    "b": [
      32,
      24.5,
      18
    ],
    "delta": [
      0,
      0,
      0
    ]
  },
  "differences": [
    "weight",
    "packaging"
  ],
  "verdict": null
}
POST/v1/declarations/checkCross-checks declared against measured, including dimensional weight.
Request
{
  "reference": "ORD-88213",
  "declared": {
    "weight_kg": 1.2,
    "dims_cm": [
      30,
      20,
      10
    ],
    "items": 1
  }
}
Response
{
  "record": "KYP-2026-000123",
  "mismatches": [
    {
      "field": "weight_kg",
      "declared": 1.2,
      "measured": 2.34
    },
    {
      "field": "dims_cm",
      "declared": [
        30,
        20,
        10
      ],
      "measured": [
        32,
        24.5,
        18
      ]
    }
  ],
  "dim_weight_kg": {
    "declared": 1.2,
    "measured": 2.82
  }
}

Parcels and Smart Track

GET/v1/parcels/{id}/timelineThe passport: custody legs, carrier events, records and gaps.
Response
{
  "parcel": "prc_01J…",
  "legs": [
    {
      "holder": "seller",
      "record": "KYP-2026-000123"
    },
    {
      "holder": "carrier_a",
      "events": 6,
      "gap_hours": 41
    },
    {
      "holder": "forwarder",
      "record": "KYP-2026-000207"
    }
  ],
  "snapshots": {
    "count": 14,
    "last_sha256": "…7be2"
  }
}
GET/v1/parcels/{id}/riskA 0–100 score with each signal and the rule that triggers it.
Response
{
  "parcel": "prc_01J…",
  "score": 72,
  "level": "high",
  "signals": [
    {
      "code": "stalled",
      "hours": 41,
      "route_p90_hours": 18
    },
    {
      "code": "weight_mismatch",
      "sealed_kg": 2.34,
      "carrier_kg": 3.1
    }
  ],
  "rules_version": "acc_rules@7"
}
GET/v1/insights/transit-times?from=ES-V&to=FR-75Real transit times by carrier and route, with their sample.
Response
{
  "carrier": "carrier_a",
  "route": "ES-V → FR-75",
  "sample": "…",
  "median_days": 3,
  "p10_p90_days": [
    2,
    6
  ],
  "confidence": "high"
}

Claim files and exports

POST/v1/dossiersBuilds the claim file: records, timeline, seals and a public link.
Request
{
  "records": [
    "KYP-2026-000123"
  ],
  "claim": {
    "type": "damage",
    "counterparty": "carrier",
    "reference": "1Z…"
  },
  "lang": "en"
}
Response
{
  "id": "dos_01J…",
  "status": "ready",
  "pdf_sha256": "…19aa",
  "share_url": "https://verify.knowyourparcel.com/d/dos_01J…"
}
POST/v1/exportsExports records and evidence for audit. Notifies with export.ready.
Request
{
  "from": "2026-10-01",
  "to": "2026-10-31",
  "format": "zip"
}
Response
{
  "id": "exp_01J…",
  "status": "processing",
  "format": "zip",
  "notify": "export.ready"
}

Vault

GET/v1/vault/transactions/{id}Every piece of evidence for a transaction, its retention and legal hold.
Response
{
  "id": "trx_01J…",
  "records": [
    "KYP-2026-000123",
    "KYP-2026-000481"
  ],
  "assets": 14,
  "attestations": [],
  "legal_hold": true,
  "retention_until": "2031-10-16",
  "integrity": {
    "last_check": "2026-10-15",
    "status": "intact"
  }
}
GET/v1/vault/transactions/{id}/manifestThe integrity manifest: every file with its fingerprint, sealed.
Response
{
  "transaction": "trx_01J…",
  "files": [
    {
      "path": "records/KYP-2026-000123.json",
      "sha256": "…a91f"
    },
    {
      "path": "assets/top.jpg",
      "sha256": "…3b9e"
    },
    "…"
  ],
  "manifest_sha256": "…5f02",
  "timestamp": "RFC 3161"
}
POST/v1/vault/transactions/{id}/access-linksAuditor link for an assessor, insurer or lawyer: scoped, expiring and logged.
Request
{
  "transactions": [
    "trx_01J…"
  ],
  "expires_in_hours": 72,
  "password": true,
  "label": "claim-4471"
}
Response
{
  "id": "val_01J…",
  "url": "https://verify.knowyourparcel.com/a/val_01J…",
  "expires_at": "2026-10-19T09:00:00Z",
  "logged": true
}
GET/v1/vault/access-logWho viewed or downloaded what, when and from where.
Response
{
  "data": [
    {
      "at": "2026-10-16T14:02:11Z",
      "actor": "user:auditor",
      "action": "manifest.downloaded",
      "target": "trx_01J…",
      "ip_country": "ES"
    },
    {
      "at": "2026-10-16T14:05:40Z",
      "actor": "access_link:val_01J…",
      "action": "asset.viewed",
      "target": "assets/top.jpg",
      "ip_country": "ES"
    }
  ],
  "next": null
}

Journeys

POST/v1/journeysPublishes a branded page for a parcel or record: passport, receipt, return or custody.
Request
{
  "subject": {
    "type": "parcel",
    "id": "prc_01J…"
  },
  "template": "passport",
  "visibility": "link",
  "expires_at": "2026-12-31",
  "lang": "auto"
}
Response
{
  "id": "jrn_01J…",
  "url": "https://verify.knowyourparcel.com/j/4kP9…",
  "template": "passport",
  "branding": "account_default"
}
PATCH/v1/journeys/{id}Changes expiry, password, template or branding.
Request
{
  "password": "set",
  "expires_at": "2027-01-31"
}
Response
{
  "id": "jrn_01J…",
  "password": true,
  "expires_at": "2027-01-31"
}

Analytics Coming soon

GET/v1/analytics/journeys?template=returnViews, conversion events and timings by template.
Response
{
  "from": "2026-10-01",
  "to": "2026-10-31",
  "template": "return",
  "views": "…",
  "events": {
    "return.capture_started": "…",
    "return.capture_completed": "…"
  },
  "median_seconds_to_complete": "…"
}
GET/v1/analytics/funnels/{template}Each template’s funnel, with the step where people drop off.
Response
{
  "template": "return",
  "steps": [
    {
      "event": "journey.viewed"
    },
    {
      "event": "return.capture_started",
      "rate": "…"
    },
    {
      "event": "return.capture_completed",
      "rate": "…"
    }
  ],
  "drop_off_step": "…"
}

Devices

GET/v1/devices/{id}/healthEach station’s health: cameras, scale, calibration and offline queue.
Response
{
  "id": "dev_01J…",
  "model": "PackNSign 60",
  "online": true,
  "last_seen_at": "2026-10-16T09:40:02Z",
  "calibration": {
    "status": "valid",
    "next_due": "2026-11-01"
  },
  "cameras": "ok",
  "scale": "ok",
  "offline_queue": 0
}
POST/v1/devices/enrollment-codesOne-time enrolment code and QR for the station to scan.
Request
{
  "label": "warehouse-desk-2"
}
Response
{
  "code": "48 21 90 17",
  "qr": "data:image/png;base64,…",
  "expires_in": 900
}

Capture sessions · SDK Coming soon

POST/v1/capture/sessionsOpens a capture session only if the phone or browser attestation passes.
Request
{
  "platform": "android",
  "attestation": "eyJ…",
  "public_key": "MCow…",
  "flow": "return"
}
Response
{
  "session": "cap_01J…",
  "trust": "attested",
  "challenge": {
    "kind": "light_pattern",
    "nonce": "…"
  },
  "expires_in": 600
}
POST/v1/capture/sessions/{id}/completeCloses the session: signatures and provenance checked, record created.
Response
{
  "session": "cap_01J…",
  "record": "KYP-2026-000481",
  "capture_trust": "attested",
  "c2pa": "valid"
}

Webhooks and log

POST/v1/webhooksSubscribes a URL to events. HMAC-SHA256 signature and retries for 72 h.
Request
{
  "url": "https://example.com/webhooks/kyp",
  "events": [
    "record.sealed",
    "alert.raised"
  ],
  "environment": "test"
}
Response
{
  "id": "whk_01J…",
  "secret": "whsec_…",
  "status": "active"
}
POST/v1/webhooks/{id}/testSends a test event and shows you the response.
Response
{
  "delivery": "dlv_01J…",
  "event": "record.sealed",
  "http_status": 200,
  "duration_ms": 182
}
GET/v1/api-log?from=2026-10-16Your own call log, including your AI agents’ calls.
Response
{
  "data": [
    {
      "at": "2026-10-16T09:41:18Z",
      "request_id": "req_01J…",
      "actor": "api_key:key_01J…",
      "route": "POST /v1/records",
      "status": 202,
      "duration_ms": 143
    },
    {
      "at": "2026-10-16T09:52:03Z",
      "request_id": "req_01J…",
      "actor": "ai_agent:claims-assistant",
      "route": "kyp_find_records",
      "status": 200,
      "duration_ms": 61
    }
  ],
  "next": "cur_01J…"
}

Account and usage

GET/v1/account/usage?month=2026-10This month’s usage against what is included, by kind, in real time.
Response
{
  "month": "2026-10",
  "license": "active",
  "included": "…",
  "used": "…",
  "projected_extra": "…",
  "by_kind": {
    "record_sealed": "…",
    "verify_public": "…",
    "track_subscription": "…"
  }
}
GET/v1/account/licenseYour licence: status, seats and active features.
Response
{
  "plan": "…",
  "status": "active",
  "billing_day": 5,
  "seats": {
    "users": "…",
    "devices": "…"
  },
  "entitlements": [
    "api",
    "webhooks",
    "mcp",
    "journeys_hosting",
    "smart_track"
  ]
}

Signed webhooks

record.sealedrecord.failedrecord.supersededparcel.handoverparcel.deliveredalert.raisedalert.resolveddevice.enrolleddevice.offlinedevice.calibration_duedossier.readyexport.readylicense.changedusage.threshold

v1 contract being designed with pilot partners. Routes and fields are illustrative; “…” marks omitted values.

Conventions

The boring parts, done well.

  • Idempotency

    Every POST that creates accepts an Idempotency-Key. Repeating the call returns the same response for 24 h, without duplicating the record.

  • RFC 9457 errors

    application/problem+json with a request_id you can find in your log. The detail comes in your language.

  • Versions

    A /v1 path and a dated KYP-Version header. Breaking changes are announced with a date and a Sunset header.

  • Pagination and limits

    Cursors instead of pages. Limits per key and per account, with standard RateLimit headers.

  • Webhooks

    HMAC-SHA256 signature with a timestamp, 8 retries with growing back-off over 72 h, and manual resend from the dashboard.

HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
Content-Language: en

{
  "type": "https://docs.knowyourparcel.com/errors/invalid-dimensions",
  "title": "Invalid dimensions",
  "status": 422,
  "detail": "dims_cm[2] must be greater than 0.",
  "request_id": "req_01J…"
}
POST /v1/records
Idempotency-Key: 6f1c2a90-8d1e-4b7a-9c3f-2b51e0d4a713

HTTP/1.1 202 Accepted
Idempotent-Replayed: true
RateLimit-Limit: 600
RateLimit-Remaining: 597
RateLimit-Reset: 41
POST /webhooks/kyp HTTP/1.1
Host: example.com
Content-Type: application/json
KYP-Signature: t=1760607679,v1=5d1f0c…e9

{ "type": "record.sealed", "data": { "id": "KYP-2026-000123" } }
const event = kyp.webhooks.verify(
  rawBody,
  req.headers['kyp-signature'],
  process.env.KYP_WEBHOOK_SECRET,
);

if (event.type === 'record.sealed') {
  await orders.attachProof(event.data.id);
}
event = kyp.webhooks.verify(
    body,
    headers["KYP-Signature"],
    os.environ["KYP_WEBHOOK_SECRET"],
)

if event.type == "record.sealed":
    orders.attach_proof(event.data.id)

Tools In development

Everything you take to your machine.

  • TypeScript SDK@knowyourparcel/sdk

    Typed client for Node and the browser: retries, automatic idempotency, pagination iterators and webhook signature checks.

  • Python SDKknowyourparcel

    Sync and async, with typed models and local record verification without calling the API.

  • CLIkyp

    Sign in, search and verify records, listen to webhooks locally, start the sandbox and simulate a station.

  • Local sandboxkyp sandbox up

    The API in test mode, a throwaway database, sample data, the dashboard and a PackNSign station simulator that sends signed records. Integrate without touching production or using quota.

  • Playgroundapp.knowyourparcel.com/playground

    A request editor with contract autocomplete, history and variables, and conversion to curl, TypeScript, Python, PHP and Go.

  • Documentation portaldocs.knowyourparcel.com

    Reference generated from the contract with “Try it” (with your test key only), guides in English and Spanish, recipes by use case, changelog and service status.

  • For machinesopenapi.json · llms.txt

    Public OpenAPI 3.1 contract, llms.txt and llms-full.txt for agents, and Postman and Bruno collections ready to import.

$ kyp login
$ kyp sandbox up
$ kyp device simulate --model packnsign-60
$ kyp records list --reference ORD-88213
$ kyp verify KYP-2026-000123
$ kyp webhooks listen --forward http://localhost:3000/webhooks/kyp

SDKs are downloaded from the dashboard; npm and PyPI publication comes later.

Ask for the technical documentation and a test key.

I want the documentation