Integration API
Machine-to-machine API for partner systems. YDONE is the source of truth; partner systems keep a cache and reconcile through delta sync and signed webhooks.
Base URL
https://ydone.appPreview/staging: https://project--6c597736-5b05-4ad7-8bd1-20789c8ec13a-dev.lovable.app
Authentication
Every request (except /health) carries three headers: x-ydone-tenant, x-ydone-timestamp (unix seconds, max 5 minutes old) and x-ydone-signature — the hex HMAC-SHA256 of `${timestamp}.${rawBody}` using the shared secret PARTNER_API_SECRET. Signatures are compared in constant time.
Server-to-server only — never in the browser
The tenant secret is a master key for every task of that tenant. Store it in your backend secret store (e.g. Lovable Cloud Secrets) and sign requests exclusively in server-side code — an edge/server function. Your frontend must never talk to YDONE directly; it calls your own authenticated backend endpoint, which then calls YDONE. Requests that look like they originate from a browser (any Origin or Sec-Fetch-* header) are rejected with 403 browser_client_forbidden, and this API returns no CORS headers. If a secret ever reached client code, rotate it immediately in YDONE.
import crypto from "node:crypto";
const BASE_URL = "https://ydone.app";
// Each tenant has its own HMAC secret. Rotate it in YDONE Platform admin → Partner portals.
const SECRET = process.env.YDONE_TENANT_SECRET;
const TENANT = "jsc-solutions";
async function call(path, method = "GET", payload) {
const rawBody = payload ? JSON.stringify(payload) : "";
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature = crypto
.createHmac("sha256", SECRET)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const res = await fetch(BASE_URL + path, {
method,
headers: {
"content-type": "application/json",
"x-ydone-tenant": TENANT,
"x-ydone-timestamp": timestamp,
"x-ydone-signature": signature,
},
...(rawBody ? { body: rawBody } : {}),
});
return res.json();
}Endpoints
| GET | /api/public/v1/health | Liveness probe — no signature required |
| POST | /api/public/v1/tasks/upsert | Batch upsert (max 200) keyed on (external_system, external_id) |
| GET | /api/public/v1/tasks?since=&limit=&cursor= | Delta sync incl. soft-deleted tasks (deleted: true) |
| GET | /api/public/v1/tasks/:id | Single task |
| POST | /api/public/v1/tasks/:id/status | Lightweight status change with optional comment |
| DELETE | /api/public/v1/tasks/:id | Soft delete |
| POST | /api/public/v1/activation-links | Create a one-time YDONE activation link for an organization admin |
| GET | /api/public/v1/activation-links | List activation links of your tenant (no tokens) |
| POST | /api/public/v1/activation-links/:id/revoke | Revoke an unused activation link |
| POST | /api/public/v1/workspaces/upsert | Mirror partner areas (phase 2) |
| GET | /api/public/v1/workspaces | List mirrored areas (phase 2) |
Upsert payload
POST /api/public/v1/tasks/upsert
{
"tenant": "jsc-solutions",
"tasks": [
{
"external_id": "0f8f2c9e-2f1a-4f21-9a0e-2b6a1d6f9a11",
"source_module": "ems_gap_item",
"source_ref_id": "GAP-2024-118",
"source_ref_label": "Gap 118 — Missing calibration record",
"source_url": "https://jsc.example.com/ems/gaps/118",
"title": "Recalibrate torque wrenches in line 3",
"description": "Calibration overdue since audit finding 118.",
"status": "in_progress",
"priority": "high",
"category": "Quality",
"assignee_name": "Mara Kessler",
"assignee_email": "mara.kessler@example.com",
"start_date": "2026-08-20",
"due_date": "2026-09-15",
"progress": 40,
"tags": ["audit", "calibration"],
"root_cause": "No recurring calibration schedule",
"effectiveness_check": "Audit re-check in 90 days",
"effectiveness_result": null,
"completed_at": null
}
]
}
200 OK
{
"results": [
{ "external_id": "0f8f…9a11", "id": "6a1c…", "action": "created", "updated_at": "2026-08-23T21:40:11.204Z" }
]
}action is created, updated or unchanged. Repeating the same payload is idempotent thanks to the unique index on (external_system, external_id).
Delta sync
GET /api/public/v1/tasks?since=2026-08-01T00:00:00Z&limit=100
200 OK
{
"tasks": [ { "id": "…", "external_id": "…", "status": "done", "deleted": false, "updated_at": "…" } ],
"next_cursor": "2026-08-23T21:40:11.204Z",
"has_more": false,
"server_time": "2026-08-23T21:41:00.000Z"
}Activation links (portal → YDONE access)
A connected portal can hand an organization admin direct YDONE access. Create a one-time link, show it as a button in your portal ("Open in YDONE") or email it. The link is bound to one email address, expires after 14 days by default and can be revoked while unused. The recipient signs in — or registers with any provider — using that email and is added to the mapped YDONE workspace with the requested role. Only the hash of the token is stored; the plain token is returned exactly once.
POST /api/public/v1/activation-links
{
"tenant": "jsc-solutions",
"email": "admin@kunde.de",
"role": "admin",
"display_name": "Mara Kessler",
"external_user_id": "portal-user-4711",
"expires_in_days": 14
}
201 Created
{
"activation_link": {
"id": "…",
"email": "admin@kunde.de",
"role": "admin",
"expires_at": "2026-09-07T09:15:00.000Z",
"url": "https://ydone.app/activate?token=8Jd…",
"token": "8Jd…"
}
}
# revoke an unused link
POST /api/public/v1/activation-links/<id>/revoke
# list links (optional ?email=)
GET /api/public/v1/activation-linksWebhooks (return channel)
Events: task.created, task.updated, task.status_changed, task.deleted. Same HMAC scheme, signed with the per-target webhook secret. Failed deliveries are retried three times with exponential backoff and logged.
POST <your target_url>
x-ydone-event: task.status_changed
x-ydone-timestamp: 1789251611
x-ydone-signature: <hmac-sha256 of "timestamp.rawBody" with the webhook secret>
{ "event": "task.status_changed", "task": { "id": "…", "external_id": "…", "status": "done" } }Error codes
| 401 | missing_auth_headers | tenant, timestamp or signature header missing |
| 401 | invalid_timestamp | timestamp is not unix seconds |
| 401 | timestamp_expired | timestamp older/newer than 5 minutes |
| 401 | invalid_signature | HMAC mismatch |
| 403 | tenant_not_allowed | unknown or deactivated tenant |
| 403 | tenant_mismatch | body tenant ≠ signed header tenant |
| 400 | invalid_json / invalid_id / invalid_since | malformed request |
| 422 | validation_failed | payload violates schema (see issues[]) |
| 404 | task_not_found | task does not exist for this tenant |
| 404 | not_found | activation link unknown, already redeemed or not yours |
| 500 | query_failed / update_failed / delete_failed | server-side error |
| 503 | api_not_configured | signing secret not configured |