Skip to content

API v1REST · JSON · HTTPS

Build on SignAnything

Send envelopes from your templates, follow every signature with webhooks, and download the signed PDFs — all from your own software.

Base URL https://api.skanything.io/v1 One host for live and test. Every response carries a SignAnything-Request-Id.
Test mode Keys that start with sa_test_ make test envelopes: signed and sealed like live ones, but no emails or texts, never billed. How test mode works

Quick start

Four steps from a new key to a signed PDF. Use a test key the first time through — nothing is sent and nothing is billed.

  1. Create a key

    In Settings › Developers, create a test key. For this guide give it envelopes:read, envelopes:write, documents:download and webhooks:manage — or Full access. Copy it now: it’s shown once.

    # Keep the key out of your code
    export SA_API_KEY="sa_test_…"
  2. Send your first envelope from a template

    Pass the template’s id, the values for its tags in data, and a person for each role in recipients. send: true sends it straight away. The Idempotency-Key makes the call safe to retry: send the same key and body again and you get the same envelope back.

    curl https://api.skanything.io/v1/envelopes \
      -H "Authorization: Bearer $SA_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: lease-1042" \
      -d '{
        "template_id": "tpl_8d2b6f0a3c9e4b7d1a5f2c8e6b0d4a9f",
        "send": true,
        "data": {
          "tenant_name": "Sam Rivera",
          "monthly_rent": "$1,850.00"
        },
        "recipients": [
          { "role": "Tenant",   "name": "Sam Rivera",   "email": "sam@example.com" },
          { "role": "Landlord", "name": "Jordan Ellis", "email": "jordan@example.com" }
        ]
      }'
    const res = await fetch("https://api.skanything.io/v1/envelopes", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.SA_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": "lease-1042",
      },
      body: JSON.stringify({
        template_id: "tpl_8d2b6f0a3c9e4b7d1a5f2c8e6b0d4a9f",
        send: true,
        data: { tenant_name: "Sam Rivera", monthly_rent: "$1,850.00" },
        recipients: [
          { role: "Tenant", name: "Sam Rivera", email: "sam@example.com" },
          { role: "Landlord", name: "Jordan Ellis", email: "jordan@example.com" },
        ],
      }),
    });
    const envelope = await res.json();
    if (!res.ok) throw new Error(`${envelope.code}: ${envelope.detail}`);
    console.log(envelope.id, envelope.status); // env_4f9c… sent
    import os
    import requests
    
    res = requests.post(
        "https://api.skanything.io/v1/envelopes",
        headers={
            "Authorization": f"Bearer {os.environ['SA_API_KEY']}",
            "Idempotency-Key": "lease-1042",
        },
        json={
            "template_id": "tpl_8d2b6f0a3c9e4b7d1a5f2c8e6b0d4a9f",
            "send": True,
            "data": {"tenant_name": "Sam Rivera", "monthly_rent": "$1,850.00"},
            "recipients": [
                {"role": "Tenant", "name": "Sam Rivera", "email": "sam@example.com"},
                {"role": "Landlord", "name": "Jordan Ellis", "email": "jordan@example.com"},
            ],
        },
    )
    envelope = res.json()
    if not res.ok:
        raise RuntimeError(f"{envelope['code']}: {envelope['detail']}")
    print(envelope["id"], envelope["status"])  # env_4f9c… sent
    using System.Net.Http.Headers;
    using System.Net.Http.Json;
    using System.Text.Json;
    
    using var http = new HttpClient { BaseAddress = new Uri("https://api.skanything.io/v1/") };
    http.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("SA_API_KEY"));
    
    using var req = new HttpRequestMessage(HttpMethod.Post, "envelopes")
    {
        Content = JsonContent.Create(new
        {
            template_id = "tpl_8d2b6f0a3c9e4b7d1a5f2c8e6b0d4a9f",
            send = true,
            data = new Dictionary<string, object> { ["tenant_name"] = "Sam Rivera", ["monthly_rent"] = "$1,850.00" },
            recipients = new[]
            {
                new { role = "Tenant", name = "Sam Rivera", email = "sam@example.com" },
                new { role = "Landlord", name = "Jordan Ellis", email = "jordan@example.com" },
            },
        }),
    };
    req.Headers.Add("Idempotency-Key", "lease-1042");
    
    using var res = await http.SendAsync(req);
    var envelope = await res.Content.ReadFromJsonAsync<JsonElement>();
    if (!res.IsSuccessStatusCode)
        throw new HttpRequestException($"{envelope.GetProperty("code")}: {envelope.GetProperty("detail")}");
    Console.WriteLine($"{envelope.GetProperty("id")} {envelope.GetProperty("status")}");
    {
      "id": "env_4f9c2d7e1b8a4c6f9e2d3b7a1c5e8f02",
      "object": "envelope",
      "livemode": false,
      "status": "sent",
      "subject": "Lease agreement — 12 Oak Street",
      "signing_order_mode": "serial",
      "template_id": "tpl_8d2b6f0a3c9e4b7d1a5f2c8e6b0d4a9f",
      "expires_at": "2026-12-04T16:05:12Z",
      "sent_at": "2026-11-04T16:05:12Z",
      "created_at": "2026-11-04T16:05:11Z",
      "updated_at": "2026-11-04T16:05:12Z",
      "labels": [],
      "recipients": [
        { "id": "rcp_1a7c3e9b5d2f4a8c6e0b9d1f3a5c7e2b", "object": "recipient", "recipient_type": "signer",
          "name": "Sam Rivera", "email": "sam@example.com", "order_group": 1, "status": "sent", "…": "…" },
        { "id": "rcp_9e3b7d1f5a2c4e8b0d6f2a9c3e7b1d5f", "object": "recipient", "recipient_type": "signer",
          "name": "Jordan Ellis", "email": "jordan@example.com", "order_group": 2, "status": "pending", "…": "…" }
      ],
      "documents": [
        { "id": "doc_6c0e4a8d2b9f4c1e7a3d5b8f0c2e6a4d", "object": "document", "name": "Lease agreement.pdf",
          "sort_order": 0, "page_count": 6, "byte_size": 184320, "scan_status": "clean" }
      ]
    }
    Signing a test envelope

    Test envelopes send no emails. To sign one, create a signing URL for each recipient with POST /v1/envelopes/{id}/recipients/{rid}/signing_url and open it. A signing URL is valid for 5 minutes. See Embedded signing.

  3. Watch it with a webhook

    Add an endpoint for envelope.completed. An endpoint takes the mode of the key that creates it, so this one gets test events only. Keep the whsec_… secret from the response — it’s shown once, and you need it to verify signatures.

    curl https://api.skanything.io/v1/webhooks \
      -H "Authorization: Bearer $SA_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://example.com/hooks/signanything",
        "event_filter": ["envelope.completed"]
      }'

    When everyone has signed, we POST the event to your URL. Answer with any 2xx within 10 seconds. data.object is the envelope, with its recipients and documents.

    {
      "id": "evt_5b1e0c9a2f7d4e3c8a6b9d0f1e2c3a4b",
      "object": "event",
      "type": "envelope.completed",
      "created": "2026-11-04T18:22:41.512Z",
      "livemode": false,
      "api_version": "v1",
      "data": {
        "object": {
          "id": "env_4f9c2d7e1b8a4c6f9e2d3b7a1c5e8f02",
          "object": "envelope",
          "livemode": false,
          "status": "completed",
          "subject": "Lease agreement — 12 Oak Street",
          "completed_at": "2026-11-04T18:22:41.512Z",
          "recipients": ["…"],
          "documents": ["…"]
        }
      }
    }
  4. Download the signed PDF

    When the envelope is completed, download its signed PDF from GET /v1/envelopes/{id}/signed_pdf: sealed, with the certificate of completion as its last pages. GET /v1/envelopes/{id}/certificate returns the certificate on its own. Both need documents:download. To get one document as it was uploaded, use …/documents/{did}/content.

    # Save the signed PDF (certificate included as its last pages)
    curl -L -o lease-signed.pdf \
      https://api.skanything.io/v1/envelopes/env_4f9c2d7e1b8a4c6f9e2d3b7a1c5e8f02/signed_pdf \
      -H "Authorization: Bearer $SA_API_KEY"
    
    # Save the certificate on its own
    curl -L -o lease-certificate.pdf \
      https://api.skanything.io/v1/envelopes/env_4f9c2d7e1b8a4c6f9e2d3b7a1c5e8f02/certificate \
      -H "Authorization: Bearer $SA_API_KEY"
    import { writeFile } from "node:fs/promises";
    
    const auth = { Authorization: `Bearer ${process.env.SA_API_KEY}` };
    const base = "https://api.skanything.io/v1/envelopes/env_4f9c2d7e1b8a4c6f9e2d3b7a1c5e8f02";
    
    for (const [part, file] of [["signed_pdf", "lease-signed.pdf"], ["certificate", "lease-certificate.pdf"]]) {
      const res = await fetch(`${base}/${part}`, { headers: auth });
      if (!res.ok) throw new Error((await res.json()).detail);
      await writeFile(file, Buffer.from(await res.arrayBuffer()));
    }
    import os
    import requests
    
    auth = {"Authorization": f"Bearer {os.environ['SA_API_KEY']}"}
    base = "https://api.skanything.io/v1/envelopes/env_4f9c2d7e1b8a4c6f9e2d3b7a1c5e8f02"
    
    for part, name in [("signed_pdf", "lease-signed.pdf"), ("certificate", "lease-certificate.pdf")]:
        res = requests.get(f"{base}/{part}", headers=auth)
        res.raise_for_status()
        with open(name, "wb") as f:
            f.write(res.content)
    using System.Net.Http.Headers;
    
    using var http = new HttpClient { BaseAddress = new Uri("https://api.skanything.io/v1/") };
    http.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("SA_API_KEY"));
    
    var pdf = await http.GetByteArrayAsync("envelopes/env_4f9c2d7e1b8a4c6f9e2d3b7a1c5e8f02/signed_pdf");
    await File.WriteAllBytesAsync("lease-signed.pdf", pdf);
    
    var cert = await http.GetByteArrayAsync("envelopes/env_4f9c2d7e1b8a4c6f9e2d3b7a1c5e8f02/certificate");
    await File.WriteAllBytesAsync("lease-certificate.pdf", cert);

Keep going