Cookie preferences

YARDtwin uses essential cookies for authentication and session management. With your consent we also set optional marketing cookies (LinkedIn Insight Tag) on our public website pages only — never inside the application, and never without your opt-in. We set no analytics cookies. Read our Privacy Policy, GDPR Policy, and Cookie Policy.

Developers

Build on YARDtwin

A documented REST API, scoped keys and signed webhooks for connecting yard and dock operations to your ERP, WMS, TMS, weighbridges and cameras.

Overview

REST API
JSON over HTTPS for bookings, gate, yard, dock, trailers, seals, departures and reporting.
OpenAPI 3.1
One machine-readable specification, with the required scope on every key-reachable operation.
Scoped API keys
Deny-by-default keys: a key can only call operations for the scopes it was granted.
Signed webhooks
HMAC-SHA256 signed events with retries, dead-letter and replay.
Sandbox tenant
A copy of your configuration to build and test against, clearly flagged as test traffic.

Quick start

  1. In the app, open Site Admin → Access & security → API keys and create a key. Pick only the scopes you need (for example appointments:read). Write scopes are marked and must be granted explicitly.
  2. Copy the key when it is shown — it starts with yt_ and is displayed once. Store it as a secret.
  3. Send it as a bearer token on every request:
HTTP header
Authorization: Bearer $YARDTWIN_API_KEY

A first call — list the bookings for a day (scope appointments:read). YARDTWIN_API_KEY is the key you copied in step 2, kept in an environment variable rather than typed into the command:

bash
curl -s "https://yardtwin.com/api/v1/appointments?date=2026-10-01&limit=10" \
  -H "Authorization: Bearer $YARDTWIN_API_KEY"
TopicDetail
Base URLhttps://yardtwin.com/api/v1
ScopesDeny-by-default. An operation not mapped to one of the key’s scopes returns 403. The scope for each operation is listed in the API reference (x-yardtwin-scope).
Rate limit500 requests per minute per client IP address across the API. Standard RateLimit headers are returned; above the limit you receive HTTP 429 — back off and retry.
ErrorsJSON bodies of the form { "error": { "message": "…", "code": "…" } } with conventional HTTP status codes.
Carrier / TMS APICarriers integrate with their own yk_ keys issued per carrier; see the API reference.

Webhooks

Add an integration in Site Admin → Integrations with your HTTPS endpoint, and choose the events it subscribes to. With no subscription an integration receives every event. Each delivery is a JSON POST:

json
{
  "event": "gate.checked_in",
  "source": "YARDtwin",
  "delivery_id": "6f1c0b0e-2d0a-4c1e-9a55-0c8f4f7e2a11",
  "timestamp": "2026-10-01T07:42:10.512Z",
  "site_id": "…",
  "data": { "…": "event-specific fields" }
}
HeaderMeaning
X-YARDtwin-Signaturesha256=<hex> — HMAC-SHA256 of the raw body + ":" + timestamp, keyed with the integration’s signing secret.
X-YARDtwin-TimestampUnix time in seconds. Re-signed with a fresh timestamp on every attempt.
X-YARDtwin-EventThe event name, e.g. gate.checked_in.
X-YARDtwin-DeliveryStable delivery id — identical across retries and replays. Also sent as Idempotency-Key and in the body as delivery_id.
X-YARDtwin-AttemptAttempt number, starting at 1.
X-YARDtwin-Replay“true” when the delivery is a replay of a dead-lettered message.
X-YARDtwin-Sandbox“true” when the event comes from a sandbox tenant (the body also carries "sandbox": true).

Verify the signature

Compute the HMAC over the exact bytes you received — do not re-serialise the JSON first — and compare in constant time.

Node.js
const crypto = require('crypto');

// Express: capture the raw body, e.g. app.use(express.raw({ type: 'application/json' }))
function verifyYardtwin(req, secret) {
  const signature = req.get('X-YARDtwin-Signature');   // "sha256=<hex>"
  const timestamp = req.get('X-YARDtwin-Timestamp');   // unix seconds
  const body = req.body.toString('utf8');              // the exact bytes received
  if (!signature || !timestamp) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; // 5-minute window

  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(body + ':' + timestamp)
    .digest('hex');
  const a = Buffer.from(signature);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Python
import hashlib, hmac, time

def verify_yardtwin(raw_body: bytes, headers: dict, secret: str) -> bool:
    signature = headers.get("X-YARDtwin-Signature", "")   # "sha256=<hex>"
    timestamp = headers.get("X-YARDtwin-Timestamp", "")   # unix seconds
    if not signature or not timestamp:
        return False
    if abs(time.time() - int(timestamp)) > 300:            # 5-minute window
        return False
    message = raw_body + b":" + timestamp.encode()
    expected = "sha256=" + hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature, expected)

Retries, dead-letter and replay

  • Respond with any 2xx within 10 seconds to acknowledge. Anything else counts as a failure.
  • Failed deliveries are retried up to 5 attempts with exponential backoff and jitter (starting at about 1 second).
  • After the last attempt the delivery is dead-lettered and visible in the integration’s delivery log. Dead letters are replayed automatically about 15 minutes later, and an administrator can replay them on demand.
  • Delivery is at-least-once: de-duplicate on delivery_id.

Event reference

62 outbound events. Legacy names are shown where an integration without a subscription still receives them.

Catalogue snapshot at build time

Appointments

  • appointment.created
    A booking was created — by ops, the carrier portal, or an inbound integration.
  • appointment.rescheduled
    A booking’s date, start or end changed (data.previous carries the old values).
  • appointment.reslot_proposed
    ETA re-slotting proposed (or, in apply mode, applied) a new slot for a booking running late (data.kind late|swap, data.from, data.to, data.paired_appointment_id, data.slip_min, data.mode).
  • appointment.cancelled
    A booking was cancelled by ops (data.reason, data.late_cancellation).
  • appointment.reminder
    A scheduled reminder for an upcoming booking was issued (data.offset_min).
  • appointment.no_show
    A booking was marked no-show — automatically after the grace period, or by an operator.
  • appointment.late_arrival
    A truck checked in later than the site’s late-arrival tolerance (data.late_by_min).
  • appointment.dock_assigned
    A dock was assigned or changed — by a planner, a reschedule, or the agent.
  • appointment.status_changedlegacy: appointment:status
    Generic status transition through the status endpoint (data.status, data.previous_status).
  • order.unverified
    A booking carries an order reference the ERP / WMS lookup did not recognise (or a duplicate of an active booking); it stays scheduled until a planner confirms it (data.orders[]).
  • waitlist.joined
    A requester joined the waitlist for a full day or window (data.waitlist_id, preferred_date, load_type, material_type, pallet_count, refusal_code, requested_by_type).
  • waitlist.offered
    A freed slot was offered to a waitlist request with an expiry (data.offered_date, offered_start, offered_end, offered_dock_id, expires_at, source cancel|no_show|reschedule|sweep|ops).
  • waitlist.fulfilled
    A waitlist offer was accepted and booked (data.appointment_id, confirmation_code, status; the appointment block is the new booking).
  • waitlist.expired
    A waitlist request expired — its dates passed or its offers were exhausted (data.reason).
  • appointment.approval_requested
    A carrier / supplier / public booking landed at pending_approval under the site’s approval policy (data.reason new_carrier|restricted_tier|unverified|all_carriers|public_link, data.channel).
  • appointment.approved
    A planner approved a pending booking (data.previous_status, data.notes, data.previous; the appointment block carries the confirmed slot).
  • appointment.rejected
    A planner rejected a pending booking — it is cancelled with a reason (data.reason_code, data.notes).
  • dg.segregation_conflict
    A dangerous-goods declaration conflicts with ADR 7.5.2.1 mixed loading — within the load itself, or with an overlapping booking on the same dock (or dock group, where the site checks adjacency). data.level warn|block, data.conflicts[].

Gate

  • gate.checked_inlegacy: gate:check_in
    A vehicle was checked in at the gate (v1 check-in or Gate v2 authorise).
  • gate.checked_outlegacy: gate:check_out
    A vehicle was checked out at the gate.
  • gate.self_checkin
    A driver submitted a self check-in from their phone; gate staff must approve it.
  • gate.exception
    A drive-through read the system could not settle: no booking (unmatched), two bookings (ambiguous), a dock-rule hold, or a refusal by carrier vetting / qualification (denied). data.exception_id, kind, decision, plate, container_number, camera_id, candidate_ids.
  • gate.refused
    Entry was refused at Gate v2 (data.reason_code).
  • gate.evidence_captured
    Photographic evidence was attached to a gate movement — tractor, trailer, seal, plate or placard, on the way in or out (data.direction, count, kinds, gate_transaction_id).
  • gate.walk_up_registered
    An unbooked arrival was registered at the gate as a walk-up booking and put on the yard record (data.purpose, source, truck_reg, checked_in). It waits in the planner's unplanned list until accepted or rejected.
  • gate.walk_up_decided
    A planner accepted or rejected an unplanned arrival (data.decision, reason, dock_id). Rejecting does not change the visit status — the truck leaves through the gate.
  • gate.geofence_arrived
    A tracked truck entered the site geofence and the booking was set to arrived automatically (data.distance_m, source, checked_in). When the site also checks in, gate.checked_in follows with source geofence.
  • gate.geofence_departed
    A finished truck stayed outside the geofence for the confirmation window and was checked out automatically (data.minutes_outside, distance_m). gate.checked_out carries the departure itself with source geofence.
  • dg.adr_lapsed
    A dangerous-goods booking presented at the gate with a missing or expired driver ADR certificate (data.status, code, expires_on, level pass|warn|blocked|overridden).

Dock & warehouse

  • unload.started
    Warehouse work started at the dock — unloading (inbound) or loading (outbound); data.load_type says which.
  • unload.completed
    Warehouse work completed at the dock (data.pallet_count).
  • incident.raised
    A hold was raised or an inspection failed (data.kind, data.reason_code).

Seals & weights

  • seal.applied
    An outbound seal was applied and recorded.
  • seal.verified
    An arrival seal was verified against the expected number and matched (or no expectation existed).
  • seal.mismatch
    A seal did not match, was tampered or damaged; the booking is on compliance hold.
  • weight.recorded
    A weighbridge reading was recorded (data.kind gate_in|gate_out|intermediate, weight_kg, source, and — where the reading came from a registered indicator — instrument_id, weighbridge_id, alibi_no, stable and legal_for_trade).
  • weight.ticket_issued
    A weighbridge ticket was issued for a visit (data.doc_number, content_hash, instrument ids, alibi numbers, gross/tare/net and whether the weighing was legal for trade).

Departure

  • departure.signed
    The departure clearance was signed (21 CFR Part 11 record; data.signature_hash).
  • departure.refused
    The driver refused to sign the departure clearance (data.reason_code).
  • departure.ready
    The load is released to depart — warehouse hand-off, WMS load_ready or the status API (data.source, data.dock_name).

Yard tasks

  • task.created
    A yard task was created — by a rule, the system check-in rule, or a planner.
  • task.completed
    A yard task was completed by a jockey or planner.
  • task.exception
    A jockey raised a move as an exception (data.code blocked | trailer_missing | damage | wrong_trailer | equipment | other, data.reason).

Trailers

  • trailer.dropped
    A trailer was dropped on the yard (drop-and-hook visit or a resident trailer registered on the twin).
  • trailer.hooked
    A trailer left the yard hooked to a tractor.
  • trailer.moved
    A trailer was moved to another position (jockey move, manual move).
  • yard.check_closed
    A yard check (lot audit) was closed — data.expected, found, wrong_spot, unexpected, missing, corrected.

Detention

  • detention.warning
    Free time is about to expire (data.remaining_min).
  • detention.started
    Free time was used up; detention is accruing (data.free_time_min, data.rate_per_hour).
  • demurrage.started
    The visit crossed the demurrage threshold and became storage.

Documents (CMR / POD)

  • cmr.issued
    A consignment note (CMR / BOL) was issued or re-issued for an outbound load (data.doc_number, version, content_hash, dataset_hash, dataset = the eFTI structured record, pdf_url, verify_url).
  • cmr.signed
    A party signed the consignment note — consignor (21 CFR Part 11) or carrier acknowledgement (data.party, data.kind, data.signature_hash, new version).
  • cmr.delivered
    The consignee acknowledged receipt through the proof-of-delivery link (data.signature_hash, data.dataset.reservations).

Life sciences

  • ls.excursion.detected
    Cold-chain excursion detected.
  • ls.consignment.quarantined
    Consignment quarantined after an excursion.
  • ls.excursion.dispositioned
    Excursion dispositioned by QA.
  • ls.custody.broken
    Chain of custody broken.
  • ls.consignment.rejected
    Consignment rejected.
  • ls.reconciliation.discrepancy
    Returns reconciliation discrepancy.

Visitors & presence

  • visitor.arrived
    A visitor, contractor or on-foot driver was registered onto the site (data.visitor_id, kind, name, company, host, via staff|kiosk).
  • visitor.departed
    A visitor signed out and left the on-site presence list (data.visitor_id, kind, name).

System

  • connector.test
    Signed connectivity ping sent by the conformance test.

Inbound events

Systems send events to YARDtwin at POST /api/v1/integrations/{integrationId}/webhook with a JSON body { "event_type": "…", "data": { … } }. Sign it with the integration’s webhook secret: X-Webhook-Signature = sha256=HMAC-SHA256(raw body + ":" + timestamp) and X-Webhook-Timestamp = unix seconds; requests older than 5 minutes are rejected.

bash
TS=$(date +%s)
BODY='{"event_type":"shipment_eta_update","data":{"reference":"PO-45001234","new_date":"2026-10-01","new_time":"09:30"}}'
SIG="sha256=$(printf '%s:%s' "$BODY" "$TS" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -hex | sed 's/^.* //')"

curl -s -X POST "https://yardtwin.com/api/v1/integrations/$INTEGRATION_ID/webhook" \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Timestamp: $TS" \
  -H "X-Webhook-Signature: $SIG" \
  -d "$BODY"
event_typeWhat it does
shipment_created / order_createdCreate a booking from a WMS, ERP or TMS shipment or order.
shipment_eta_updateMove a booking’s date or start time, matched by its reference number.
shipment_statusUpdate the status of a booking from the sending system.
shipment_cancelledCancel a booking.
sap_inbound_deliverySAP EWM inbound (or outbound) delivery → booking; raw DELVRY07 IDoc envelopes are flattened automatically.
gr_status / gr_posted / gr_rejectedGoods-receipt acknowledgement from SAP (material document number or rejection).
gi_status / gi_posted / gi_rejected / goods_issueGoods-issue acknowledgement for outbound loads.
gr_reversed / gi_reversedA goods receipt or goods issue was reversed in SAP; the reversal document is recorded.
tu_registeredSAP transport-unit acknowledgement for a registered truck.
door_statusA door was blocked or released in the warehouse system.
door_assignmentThe WMS assigns (or changes) the dock door for a booking.
load_readyThe WMS reports the load is ready to depart.
staging_completeGoods are staged for loading.
customs_releaseCustoms clearance status for a held load.
master_dataImport carriers, materials or other master data.
plate_read / vision_readA camera (ANPR / computer vision) read a plate or container number.
vehicle_detected / occupancy / inspection_eventCamera or sensor events for the yard.
position_reportVehicle GPS position from a fleet or telematics system.
tag_reportPosition from a trailer or asset tag.
temperature_readingCold-chain temperature reading from a data logger.
weight_reading / weighbridge_weightGross or tare reading from a weighbridge.
ecmr_referenceLink an external e-CMR reference to a consignment note.
edi_asnAdvance ship notice as raw EDIFACT DESADV or X12 856 text in the "edi" field; creates or updates the booking, order lines and expected SSCCs.
dock_sensorDock equipment state: door, leveller, vehicle restraint, occupancy or dock light (warns when a door opens without the restraint engaged).

The reply is 200 with { "received": true, "logId": "…" } and, where a field was interpreted rather than read directly, a warnings[] list. Every message appears in the integration’s log in the app.

Resources

ResourceWhere
OpenAPI 3.1 specification/api/v1/openapi.json
Interactive API reference/api/v1/docs
Postman collection/api/v1/postman
Sandbox tenantAvailable on Professional and Enterprise — request it via admin@yardtwin.com
SAP EWM integration kitConfiguration manual, API specification, request collection and CPI mapping reference — on request via admin@yardtwin.com
Integrations overviewIntegrations

Honest boundaries

  • EDI (EDIFACT DESADV, X12 856) is accepted over HTTPS webhooks. There is no AS2 or VAN connection yet.
  • SAP IDoc mapping runs on the SAP side (for example in SAP Integration Suite / CPI); YARDtwin receives and sends JSON over HTTPS.
  • There are no client SDKs yet — generate a client from the OpenAPI specification if you need one.

Building an integration for several customers? Join the partner programme

Hi there! Start your free trial in 2 minutes — I'll help you set everything up!