# API and webhooks

> Call your app from other servers and tools with API keys, download its OpenAPI spec, and get record changes pushed to your URL.


**Dashboard → API & webhooks** shows your app's ready-made API: the base URL, every endpoint for your tables, functions and agents, and code you can copy.

## API keys

Create a key to call the API from your own server, Zapier, Make or a script:

```bash
curl "https://<your-app>/_ds/entities/Order?limit=10" \
  -H "Authorization: Bearer dsa_…"
```

A key has **admin access** to your app's data (it ignores data permissions), so keep it on a server and never put it in browser code. The full key is shown once. Revoke it at any time; anything using it stops working immediately. An invalid key gets `401`.

Without a key, requests run with the permissions of whoever makes them: a signed-in user's token, or a visitor.

## OpenAPI spec

**OpenAPI spec** downloads an OpenAPI 3.1 file describing every table (with its fields), backend function and agent. Import it into Postman or Insomnia, or generate a client library from it.

## Webhooks

Add a URL and choose events, and Draftship POSTs every matching record change to it:

| Events | Sends |
|---|---|
| `*` | every change in every table |
| `Order.*` | creates, updates and deletes of Order |
| `Order.create` | only new orders |

The body looks like this:

```json
{
  "event": "Order.create",
  "delivery_id": "…",
  "app_id": "…",
  "entity": "Order",
  "type": "create",
  "id": "…",
  "data": { "id": "…", "total": 42, "created_by": "ada@example.com", "created_date": "…" },
  "timestamp": "2026-11-02T10:00:00.000Z"
}
```

Every request carries `X-Draftship-Signature: sha256=<hex>`, an HMAC-SHA256 of the raw body using your webhook's signing secret (shown once when you add it). Check it before trusting the request:

```ts
import { createHmac, timingSafeEqual } from "node:crypto";

const expected = "sha256=" + createHmac("sha256", process.env.DRAFTSHIP_WEBHOOK_SECRET!).update(rawBody).digest("hex");
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["x-draftship-signature"] ?? ""));
```

Failed deliveries (timeouts or non-2xx responses) are retried after 10 seconds, 1 minute and 5 minutes, and then logged in **Logs**. **Test** sends a `test.ping` event. Webhook URLs must use `https://` and can't point to private network addresses. Records created by the [testing agent](/docs/building/testing-agent) never trigger webhooks.
