SecretWolf

API & webhooks

Create secrets and requests, query the audit log, and receive signed webhooks from your own tools. The API is available on the Business plan. The live, always- current spec is at app.secretwolf.io/api-docs (OpenAPI JSON at /api-docs/v1/openapi.json).

Building an AI agent? There's an anonymous, pay-per-use endpoint that needs no key — the agent pays $0.10 in USDC over x402 per secret. The reference is further down this page; the worked examples are on Pay-per-secret API for AI agents.

Base URL

https://app.secretwolf.io

Authentication

Send your API key as a Bearer token. Keys look like sw_live_… and are created in your workspace settings. The key identifies your workspace; keep it secret and rotate it if exposed.

Authorization: Bearer sw_live_your_key_here

A missing or invalid key returns 401; a key whose workspace isn't on the Business plan returns 403.

Create a secret

POST /api/v1/secrets

Body fields (all optional): text, isOneTimeUse (default true), expiryHours, maxViews, notifyOnView, templateKey, fields (a string-to-string map for templates), requireOtp, recipientEmail, reference, ticketReference. The response is metadata only — never the secret.

curl -X POST https://app.secretwolf.io/api/v1/secrets \
  -H "Authorization: Bearer sw_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"text":"hunter2","isOneTimeUse":true,"expiryHours":24,"requireOtp":true,"recipientEmail":"[email protected]"}'

Returns:

{
  "url": "https://acme.secretwolf.io/s/9fA2hTq7Kd",
  "id": "9fA2hTq7Kd"
}

Same call in C#:

using System.Net.Http.Json;
using System.Net.Http.Headers;

using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", "sw_live_your_key_here");

var body = new
{
    text = "hunter2",
    isOneTimeUse = true,
    expiryHours = 24,
    requireOtp = true,
    recipientEmail = "[email protected]"
};

var res = await http.PostAsJsonAsync($"{baseUrl}/api/v1/secrets", body);
res.EnsureSuccessStatusCode();
var created = await res.Content.ReadFromJsonAsync<CreatedSecret>();
Console.WriteLine(created!.Url);

record CreatedSecret(string Url, string Id);

Create a request

POST /api/v1/requests

Ask someone to send you a secret through a branded form. Fields: label (required), templateKey, recipientEmail, expiryDays (default 7). Requires the Team plan or higher.

curl -X POST https://app.secretwolf.io/api/v1/requests \
  -H "Authorization: Bearer sw_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"label":"Your CMS admin login","recipientEmail":"[email protected]","expiryDays":7}'

Returns:

{
  "id": "3f2e…",
  "url": "https://acme.secretwolf.io/r/Vb7…",
  "expiresAt": "2026-08-02T09:41:00Z"
}

Query the audit log

GET /api/v1/audit

Query parameters: type (an event type such as SecretViewed), secretId, from, to, page (default 1), pageSize (default 50, max 200). Metadata only — never the secret. Requires the Team plan or higher.

curl "https://app.secretwolf.io/api/v1/audit?type=SecretViewed&page=1&pageSize=50" \
  -H "Authorization: Bearer sw_live_your_key_here"

Returns:

{
  "total": 128,
  "page": 1,
  "pageSize": 50,
  "events": [
    {
      "createdAt": "2026-07-26T09:44:12Z",
      "eventType": "SecretViewed",
      "actor": "recipient",
      "secretId": "9fA2hTq7Kd",
      "recipientLabel": "[email protected]",
      "ipAddress": "203.0.113.9",
      "userAgent": "Mozilla/5.0 …"
    }
  ]
}

Secret status

GET /api/v1/secrets/{id}/status

Returns the live status of a secret — active, or a terminal viewed, expired or revoked once the payload is gone. Never returns the secret itself.

Webhooks

Register an HTTPS endpoint and a whsec_… signing secret to receive events: SecretViewed, SecretExpired and RequestFulfilled. Every delivery is a POST with an X-SW-Event header and an X-SW-Signature header, and is retried with backoff on failure. Payloads are metadata only.

POST https://your-app.example.com/hooks/secretwolf
X-SW-Event: SecretViewed
X-SW-Signature: t=1753520652,v1=1b2c3d4e…

{
  "event": "SecretViewed",
  "occurredAt": "2026-07-26T09:44:12Z",
  "data": { "secretId": "9fA2hTq7Kd" }
}

Verify the signature before trusting a delivery. The header is t={unix},v1={hex}, and the signed value is {t}.{rawBody} using HMAC-SHA256 keyed by your signing secret:

// Signature header: X-SW-Signature: t={unixSeconds},v1={hexHmac}
// Signed value is "{t}.{rawBody}", HMAC-SHA256 keyed by your whsec_ signing secret.
static bool Verify(string signingSecret, string header, string rawBody)
{
    var parts = header.Split(',');                 // ["t=…", "v1=…"]
    var t   = parts[0]["t=".Length..];
    var sig = parts[1]["v1=".Length..];

    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(signingSecret));
    var expected = Convert.ToHexString(
        hmac.ComputeHash(Encoding.UTF8.GetBytes(t + "." + rawBody))).ToLowerInvariant();

    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(sig));
}

Rate limits

The v1 API allows 60 requests per minute per key. Over the limit returns 429 with a short plain-text body — back off and retry.

Errors

Validation and authentication errors return a JSON { "error": "…" } with a 400, 401, 403 or 404. Plan and processing failures return an RFC 7807 application/problem+json body. Throttling returns a plain-text 429.

Machine payments (x402)

A second, separate endpoint, with different rules from everything above: no API key, no account, and payment settled per call in USDC over the x402 protocol. Positioning and worked examples are on the AI agents page — this is the reference.

Create a secret

POST https://app.secretwolf.io/api/x402/secrets

Body is either text (up to 10,000 characters) or templateKey plus fields, where templateKey is one of website-login, dns-account, api-key or custom. Agent secrets are always one-time-use, with no attachments and no recipient verification. A malformed body is rejected with 400 before any payment is taken, so a call that couldn't have produced a secret is never charged. The response is the same { "url", "id" } shape as the v1 create endpoint; the plaintext is never returned and never logged.

The handshake

This endpoint speaks x402 version 2:

  1. POST with no payment. The server answers 402 with the payment requirements base64-encoded in a PAYMENT-REQUIRED response header.
  2. Your client signs a payment authorisation and retries the same POST with it in a PAYMENT-SIGNATURE request header. The legacy v1 name X-PAYMENT is still accepted.
  3. A facilitator verifies and settles on-chain; the server returns 200 with the link, and the settlement receipt in a PAYMENT-RESPONSE header.

The challenge offers one payment method: scheme exact, network eip155:8453 (Base mainnet in CAIP-2 form), asset USDC, at $0.10 per secret. Decoded, it looks like this:

{
  "x402Version": 2,
  "resource": {
    "url": "https://app.secretwolf.io/api/x402/secrets",
    "serviceName": "SecretWolf",
    "tags": ["secret", "one-time", "credentials"]
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "amount": "100000",
      "asset": "0x…",
      "payTo": "0x…",
      "maxTimeoutSeconds": 60,
      "extra": { "name": "USD Coin", "version": "2" }
    }
  ]
}

Read asset, payTo and extra from the live challenge rather than hard-coding them. amount is in atomic USDC units (6 decimals). extra is the EIP-712 domain you sign against and it comes from the token contract, so its name is the contract's own — USD Coin, not the USDC ticker used everywhere else on this page.

Consume a secret (agent to agent)

POST https://app.secretwolf.io/api/x402/secrets/{id}/reveal

Returns the value as JSON exactly once, then hard-deletes it — a second call is 404. Free, because creation was already paid and the 256-bit id in the link is itself the capability, so no key and no payment apply. It is POST-only by design: a bare GET can never consume a secret, so a link scanner or preview bot can't burn one. Rate-limited per IP. A free-text secret returns { "text": "…" }; a templated one returns { "templateKey", "fields" }.

Identifying your agent (optional)

If your agent implements Web Bot Auth, send the absolute https URL of your HTTP Message Signatures key directory:

Signature-Agent: "https://agent.example.com"

We record it against the payment so a repeat caller stays recognisable across wallet addresses. It is optional, it doesn't change the price, and nothing is gated on it — we don't verify the signature, so it is a label rather than a credential. Anything that isn't an absolute https URL is ignored.

Discovery and limits

The challenge carries discovery metadata (service SecretWolf, tags secret, one-time, credentials) so x402-native agents can find the endpoint and read its price without prior knowledge. Unpaid challenges are limited to 30 per minute per IP — the payment itself is the real throttle. Settled payments are non-refundable; requests that fail validation are never charged. Every agent-created link carries the "Powered by SecretWolf" footer.

OpenAPI for the anonymous endpoints: /openapi/x402.json — served from this host so agents and crawlers can actually read it, and generated from the same constants as the prices above.

Build it into your stack

API keys, webhooks and the audit API are on the Business plan.

See pricing