Signing quick start
Everything here runs in test mode: no SMS is sent, the code is always 424242, and the certificate issued is a test one.
1. Get a secret key
Section titled “1. Get a secret key”In the console, open Signing → API keys in your sandbox organization and create a secret key. It is shown once. Keep it in an environment variable, never in code:
export EPAHICHAN_SECRET_KEY=sk_test_...export EPAHICHAN_API=https://api.epahichan.com2. Upload the documents
Section titled “2. Upload the documents”Upload each PDF. For a signer who holds no certificate yet, one of them is their certificate application form (Schedule 5).
curl $EPAHICHAN_API/v1/files \ -H "Authorization: Bearer $EPAHICHAN_SECRET_KEY" \ -H "Content-Type: application/pdf" \ --data-binary @schedule-5.pdf# {"id": "file_8d2f...", "object": "file", "pages": 2, "sha256": "...", ...}import { readFile } from 'node:fs/promises';
const api = process.env.EPAHICHAN_API;const key = process.env.EPAHICHAN_SECRET_KEY;
async function upload(path) { const response = await fetch(`${api}/v1/files`, { method: 'POST', headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/pdf' }, body: await readFile(path), }); if (!response.ok) throw new Error((await response.json()).error.message); return (await response.json()).id;}
const schedule5 = await upload('schedule-5.pdf');const agreement = await upload('loan-agreement.pdf');import os, requests
API = os.environ["EPAHICHAN_API"]KEY = os.environ["EPAHICHAN_SECRET_KEY"]auth = {"Authorization": f"Bearer {KEY}"}
def upload(path): with open(path, "rb") as f: r = requests.post(f"{API}/v1/files", headers={**auth, "Content-Type": "application/pdf"}, data=f) r.raise_for_status() return r.json()["id"]
schedule5 = upload("schedule-5.pdf")agreement = upload("loan-agreement.pdf")3. Create the signing request
Section titled “3. Create the signing request”signature places the visible signature: points from the page’s top-left corner; page counts from 1, and -1 is the last page. Send an Idempotency-Key so a retry never creates a second request.
curl $EPAHICHAN_API/v1/signing-requests \ -H "Authorization: Bearer $EPAHICHAN_SECRET_KEY" \ -H "Idempotency-Key: loan-LN-2026-0042" \ -H "Content-Type: application/json" \ -d '{ "signer": { "name": "Sita Sharma", "phone": "+9779841234567", "identity": { "type": "citizenship", "number": "27-01-75-01234", "issuing_office": "Kathmandu", "issued_on": "2015-04-12" } }, "documents": [ { "file": "file_8d2f...", "name": "Certificate application", "purpose": "certificate_application" }, { "file": "file_1c9a...", "name": "Loan agreement LN-2026-0042", "signature": { "page": -1, "x": 360, "y": 700, "width": 180, "height": 60 } } ], "reference": "LN-2026-0042" }'const response = await fetch(`${api}/v1/signing-requests`, { method: 'POST', headers: { Authorization: `Bearer ${key}`, 'Idempotency-Key': 'loan-LN-2026-0042', 'Content-Type': 'application/json', }, body: JSON.stringify({ signer: { name: 'Sita Sharma', phone: '+9779841234567', identity: { type: 'citizenship', number: '27-01-75-01234', issuing_office: 'Kathmandu', issued_on: '2015-04-12' }, }, documents: [ { file: schedule5, name: 'Certificate application', purpose: 'certificate_application' }, { file: agreement, name: 'Loan agreement LN-2026-0042', signature: { page: -1, x: 360, y: 700, width: 180, height: 60 } }, ], reference: 'LN-2026-0042', }),});const request = await response.json();// request.id: "sr_...", request.status: "pending", request.client_secret: "sr_..._secret_..."r = requests.post( f"{API}/v1/signing-requests", headers={**auth, "Idempotency-Key": "loan-LN-2026-0042"}, json={ "signer": { "name": "Sita Sharma", "phone": "+9779841234567", "identity": {"type": "citizenship", "number": "27-01-75-01234", "issuing_office": "Kathmandu", "issued_on": "2015-04-12"}, }, "documents": [ {"file": schedule5, "name": "Certificate application", "purpose": "certificate_application"}, {"file": agreement, "name": "Loan agreement LN-2026-0042", "signature": {"page": -1, "x": 360, "y": 700, "width": 180, "height": 60}}, ], "reference": "LN-2026-0042", },)r.raise_for_status()request = r.json()4. Accept and confirm, as the signer
Section titled “4. Accept and confirm, as the signer”In production your app takes these steps with the publishable key and the request’s client_secret (see the banking app guide). A secret key may take them too, which is handy for a first test:
SR=sr_... # the request's id
curl -X POST $EPAHICHAN_API/v1/signing-requests/$SR/accept \ -H "Authorization: Bearer $EPAHICHAN_SECRET_KEY"# "status": "awaiting_code"; in test mode "code": {"test_code": "424242", ...}
curl $EPAHICHAN_API/v1/signing-requests/$SR/confirm \ -H "Authorization: Bearer $EPAHICHAN_SECRET_KEY" \ -H "Content-Type: application/json" -d '{"code": "424242"}'# "status": "completed"5. Download the signed documents
Section titled “5. Download the signed documents”curl $EPAHICHAN_API/v1/signing-requests/$SR \ -H "Authorization: Bearer $EPAHICHAN_SECRET_KEY"# each document has "signed_at", "signed_sha256" and "content_url"
curl -o signed-agreement.pdf "$EPAHICHAN_API/v1/signing-requests/$SR/documents/doc_.../content" \ -H "Authorization: Bearer $EPAHICHAN_SECRET_KEY"Open the PDF in any reader: the signature is at the place you chose, and the signature panel names the signer. In test mode it also says the signature is a test one.
The console’s Signing requests page shows the same request, its events and both documents, and Logs shows each call you made.