Developer docs

API reference

A small, stable JSON API for reading campaigns and issues, filing issues from your own tooling, and subscribing to events. It is the same surface the DeploySeal Zapier app is built on. Everything lives under /api/v1; the machine-readable contract is at /api/v1/openapi.json on the API host (use your own host if self-hosted).

Authentication

Create an organisation API key under Settings → Integrations → API keys (Owner or Admin). The key is shown once; DeploySeal keeps only a hash. Send it as a bearer token:

curl https://api.deployseal.com/api/v1/campaigns \
  -H "Authorization: Bearer ds_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

# X-Api-Key is accepted too
curl https://api.deployseal.com/api/v1/me -H "X-Api-Key: ds_live_…"
  • Keys start with ds_live_ in production and ds_test_ elsewhere.
  • A key authenticates only against /api/v1. It cannot sign in, manage members or billing, or reach the dashboard API — and a dashboard session cannot call /api/v1.
  • Revoking a key takes effect immediately; anything using it gets a 401.

GET /api/v1/me

{
  "organization": { "id": "0c2a…", "name": "Acme Commerce" },
  "key": { "id": "9d1f…", "name": "Zapier", "scopes": ["read", "write", "webhooks"] }
}

Scopes

A key carries any combination of three scopes. They are independent — write does not imply read. A call without the right scope gets a 403.

ScopeGrants
readList reviews and issues, read issue detail and event samples.
writeCreate issues; report platform inventory.
webhooksCreate, list and delete webhook endpoints — what the Zapier triggers use.

Endpoints

EndpointScopeWhat it does
GET /api/v1/meanyThe calling key and its organisation.
GET /api/v1/campaignsreadEvery campaign, newest first. Optional ?status=Active|Draft|Paused|Closed.
GET /api/v1/campaigns/{id}/issuesreadIssues of a campaign, most severe first. ?page=1&pageSize=50 (max 200), ?status=, ?severity=.
POST /api/v1/campaigns/{id}/issueswriteFile an issue. Reporter is recorded as "API key {name}".
GET /api/v1/issues/{id}readIssue detail with status history.
POST /api/v1/sites/{publicKey}/inventorywriteRecord the platform inventory (installed plugins and versions) of the environment behind a site key. Idempotent.
GET /api/v1/sites/{publicKey}/inventoryreadRecent inventory snapshots of that environment, newest first. ?limit=10 (max 50).
GET /api/v1/eventsreadNames of the webhook events emitted today.
GET /api/v1/events/{event}/samplereadA representative delivery envelope for one event (what Zapier shows while you build a Zap).
GET /api/v1/webhookswebhooksWebhook endpoints of the organisation.
POST /api/v1/webhookswebhooksSubscribe a URL (REST hook). Returns the signing secret once.
DELETE /api/v1/webhooks/{id}webhooksUnsubscribe.

GET /api/v1/campaigns

readinessLevel is the launch policy's verdict (ReadyToLaunch, ReadyWithIssues, AtRisk, DoNotLaunch); gate folds in required sign-offs (cleared, awaiting-signoff, not-cleared).

[
  {
    "id": "1a2b3c4d-5e6f-4a8b-9c0d-1e2f3a4b5c6d",
    "name": "Spring checkout release",
    "status": "Active",
    "siteId": "5f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
    "siteName": "acme-store.example",
    "readinessLevel": "ReadyWithIssues",
    "gate": "awaiting-signoff",
    "createdAt": "2026-08-30T09:12:44.101Z"
  }
]

GET /api/v1/campaigns/{id}/issues

{
  "items": [
    {
      "id": "9b2c5d4e-1f2a-4b3c-8d7e-6f5a4b3c2d1e",
      "seqNumber": 7,
      "title": "Checkout button unresponsive",
      "type": "Bug",
      "severity": "High",
      "status": "New",
      "assigneeName": null,
      "pageUrl": "https://staging.example.com/checkout",
      "createdAt": "2026-09-03T14:21:07.415Z"
    }
  ],
  "page": 1,
  "pageSize": 50,
  "total": 1,
  "hasMore": false
}

POST /api/v1/campaigns/{id}/issues

Goes through the same pipeline as an issue filed from the dashboard: it gets the next per-campaign number, fires IssueCreated to webhooks and chat channels, and syncs to connected issue trackers.

POST /api/v1/campaigns/{id}/issues
Content-Type: application/json

{
  "title": "Promo code field accepts whitespace",
  "description": "Entering \"  SPRING10 \" is rejected but the same code without spaces works.",
  "severity": "Medium",          // Low | Medium | High | Critical
  "type": "Bug",                 // Bug | Feedback | Question | Suggestion
  "pageUrl": "https://staging.example.com/cart"
}

201 Created
{ "id": "…", "seqNumber": 8 }

POST /api/v1/sites/{publicKey}/inventory

For platform plugins (nopCommerce today): report which extensions are installed, at which versions, so the readiness report can print the inventory current at each campaign's testing window and diff it against the previous sealed version. publicKey is the environment key from the install snippet (a legacy site key resolves to the site's default environment). The key's organisation must own that environment — anything else is a 404. The server canonicalises the items (sorted by systemName) and fingerprints them; the same items with the same capturedAt answer 200 with the existing snapshot instead of storing a duplicate. Field problems come back as a 400 with an errors map.

POST /api/v1/sites/{publicKey}/inventory
Content-Type: application/json

{
  "platform": "nopcommerce",          // lower-case, up to 32 chars
  "platformVersion": "4.90.0",
  "buildMarker": "2026.09.17-a1b2c3",  // optional — the build the plugin resolved
  "capturedAt": "2026-09-17T06:00:00Z",
  "items": [                           // up to 500; systemName is the key
    { "systemName": "Payments.PayPalCommerce", "name": "PayPal Commerce", "version": "4.90.1", "enabled": true },
    { "systemName": "Widgets.GoogleAnalytics", "name": "Google Analytics", "version": "4.90.0", "enabled": false }
  ]
}

201 Created                            // 200 OK when this exact snapshot was already on record
{ "id": "…", "capturedAt": "2026-09-17T06:00:00Z", "itemCount": 2 }

REST hooks (webhooks)

Subscribing through the API creates the same endpoint as the settings page, signed the same way and delivered through the same durable outbox. Endpoints created here are labelled via API in Settings → Integrations → Outbound Webhooks. Payloads, signature verification and retries are documented in the webhook events reference; GET /api/v1/events/{event}/sample returns a sample envelope for any emitted event, built by the same code as the real deliveries.

POST /api/v1/webhooks
Content-Type: application/json

{
  "url": "https://hooks.zapier.com/hooks/standard/…",
  "events": ["IssueCreated"],      // empty or omitted = every event
  "name": "Zapier — new issue"     // optional label for the settings page
}

201 Created
{
  "id": "…",
  "url": "https://hooks.zapier.com/hooks/standard/…",
  "events": ["IssueCreated"],
  "secret": "base64…"              // HMAC-SHA256 signing secret, shown once
}

Rate limits & errors

  • 600 requests per minute per key. Over the limit you get a 429 with a Retry-After header in seconds.
  • Errors are RFC 7807 application/problem+json with a traceId you can quote to support. Validation failures are 422 with a per-field errors map.
  • Enum values are the same strings the dashboard shows (High, InProgress, …). Timestamps are UTC ISO-8601.
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json

{
  "status": 403,
  "title": "Access denied.",
  "detail": "This API key does not carry the scope this endpoint requires.",
  "traceId": "0HN4…"
}

HTTP/1.1 429 Too Many Requests
Retry-After: 37
Content-Type: application/problem+json

{ "status": 429, "title": "Too many requests.", "detail": "Rate limit exceeded. Retry after 37 seconds.", "traceId": "…" }

Zapier

The DeploySeal app for Zapier connects with an API key that has all three scopes. Triggers (new issue, issue status changed, campaign launched, report sealed, tester completed) are REST hooks on top of the endpoints above; the Create issue action and the Find review search use the review and issue endpoints. If you prefer not to install the app, Webhooks by Zapier → Catch Hook works with any endpoint you add in the settings page.

Ready to build? Keys live in your organisation settings.

Open API keys