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.
https://api.skanything.io/v1
One host for live and test. Every response carries a SignAnything-Request-Id.
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.
-
Create a key
In Settings › Developers, create a test key. For this guide give it
envelopes:read,envelopes:write,documents:downloadandwebhooks:manage— or Full access. Copy it now: it’s shown once.# Keep the key out of your code export SA_API_KEY="sa_test_…"
-
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 inrecipients.send: truesends it straight away. TheIdempotency-Keymakes 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… sentimport 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… sentusing 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 envelopeTest envelopes send no emails. To sign one, create a signing URL for each recipient with
POST /v1/envelopes/{id}/recipients/{rid}/signing_urland open it. A signing URL is valid for 5 minutes. See Embedded signing. -
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 thewhsec_…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
2xxwithin 10 seconds.data.objectis 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": ["…"] } } } -
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}/certificatereturns the certificate on its own. Both needdocuments: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);