Metadata-Version: 2.1
Name: signedly
Version: 0.1.0
Summary: Signedly API
Home-page: https://github.com/signedly/signedly-sdks
Author: Signedly
Author-email: info@signedly.com
License: MIT
Keywords: OpenAPI,OpenAPI-Generator,Signedly API
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: urllib3<3.0.0,>=1.25.3
Requires-Dist: python-dateutil>=2.8.2
Requires-Dist: pydantic>=2
Requires-Dist: typing-extensions>=4.7.1

     The Signedly REST API lets your system upload documents, add signatories and fields, send them for signing and download the signed result. Every path starts with &#x60;/ap1/&#x60;, and every request and response body is JSON unless an endpoint says otherwise (the uploads are &#x60;multipart/form-data&#x60;).  ## Authentication  1. In the Signedly web app, open **Account &amp; Settings → API** and generate a    **client ID** and **client secret** for your organisation. 2. &#x60;POST /ap1/token/&#x60; with them. You get an &#x60;access_token&#x60; (valid **2 hours**) and    a &#x60;refresh_token&#x60; (valid **7 days**). While both are still valid, calling    &#x60;/ap1/token/&#x60; again returns the same pair. 3. Send the access token on every other call:    &#x60;Authorization: Bearer &lt;access_token&gt;&#x60;. 4. When the access token expires, &#x60;POST /ap1/refreshtoken/&#x60; with the refresh token    for a new access token. When the refresh token has expired too, call    &#x60;/ap1/token/&#x60; again.  &#x60;/ap1/token/&#x60; and &#x60;/ap1/refreshtoken/&#x60; allow 60 calls a minute per IP address; more answer 429. A missing, unknown or expired access token answers 403 (&#x60;\&quot;Client not found\&quot;&#x60; or &#x60;\&quot;Access token expired\&quot;&#x60;).  The API acts for your **organisation&#39;s admin account**: documents you upload belong to it and appear in its Signedly web app, and you can reach only that account&#39;s documents, templates, signatories and fields. Another organisation&#39;s IDs answer 404, as if they didn&#39;t exist.  ## Plans  What the API allows depends on the Signedly plan of your **organisation&#39;s admin account** (Account &amp; Settings → Account &amp; Plan). The same rules apply to live and test keys.  | What | Free | Personal Pro | Business Pro | Teams | |---|---|---|---|---| | Generate live and test keys (Account &amp; Settings → API) | – | ✓ | ✓ | ✓ | | Every endpoint, &#x60;/ap1/token/&#x60; and &#x60;/ap1/refreshtoken/&#x60; included | – | ✓ | ✓ | ✓ | | &#x60;require_email_otp&#x60; on &#x60;POST /ap1/signatories/&#x60; and &#x60;POST /ap1/signatories/bulk/&#x60; | – | ✓ | ✓ | ✓ | | Bulk send: &#x60;GET /ap1/bulk-sends/columns/&#x60; and &#x60;POST /ap1/bulk-sends/&#x60; | – | – | – | ✓ |  The API is part of the paid plans. On Free (or with no plan) every call answers 403 &#x60;\&quot;... Your plan doesn&#39;t include API access.\&quot;&#x60;, with live or test keys, also with a token or keys made earlier; they work again once the plan is paid. On a paid plan, a feature it doesn&#39;t include answers 400 with &#x60;\&quot;Your plan doesn&#39;t include ...\&quot;&#x60; and changes nothing, and &#x60;GET /ap1/bulk-sends/{id}/&#x60; still reports bulk sends already made.  Live documents also need an API contract with Signedly. Without one, or with an unpaid API invoice, &#x60;POST /ap1/upload/&#x60; (except templates), &#x60;/ap1/upload_app/&#x60;, &#x60;/ap1/documents/fromtemplate/&#x60; and &#x60;/ap1/bulk-sends/&#x60; answer 402 with live keys. Test keys work without a contract (see Sandbox).  ## Sending a document for signing  1. &#x60;POST /ap1/upload/&#x60; a PDF or DOCX (&#x60;type&#x60; 2). The document is prepared in the    background: find it in &#x60;GET /ap1/documents/?name&#x3D;&lt;name&gt;&#x60; (its &#x60;upload.id&#x60; is    the upload&#39;s &#x60;id&#x60;), and wait until &#x60;GET /ap1/documents/{id}/pages/&#x60; lists its    pages. 2. &#x60;POST /ap1/signatories/&#x60; (one) or &#x60;POST /ap1/signatories/bulk/&#x60; (several) to    add the people who sign. 3. &#x60;POST /ap1/documents/fields/&#x60; to place the fields each signatory fills in. 4. Send it:    - &#x60;POST /ap1/document/send/&#x60; emails every signatory a signing link (with      ordered signing, only the first; each next one is emailed in      turn), or    - &#x60;POST /ap1/document/send_url/&#x60; sends no email and returns the first      signatory&#39;s signing link, for you to show or send yourself. 5. Poll &#x60;GET /ap1/documents-status-list/&#x60; (or &#x60;GET /ap1/documents/&#x60;) until the    status is 6 (Completed), then &#x60;GET /ap1/document/download-link/&#x60; for the    signed PDF and &#x60;GET /ap1/document/download-audit-link/&#x60; for its audit trail.  **From a template:** upload with &#x60;type&#x60; 4 and a &#x60;template_name&#x60; (or make the template in the web app), place its fields with &#x60;POST /ap1/templates/fields/&#x60;, then for each signing make a document with &#x60;POST /ap1/documents/fromtemplate/&#x60;, add its signatory, and send it.  **One signer, one call:** &#x60;POST /ap1/upload_app/&#x60; uploads a PDF or DOCX for one signer and returns their signing link in &#x60;redirect_url&#x60;; the signer places their own signature. After signing they go to the &#x60;redirect_url&#x60; you sent, and the signed PDF and its audit trail are emailed to &#x60;final_email&#x60; (otherwise to your organisation&#39;s admin).  ## Quick start with curl  Each step uses an ID from the step before. &#x60;$API&#x60; is this server, e.g. &#x60;https://l0ads.signedly.com&#x60;.  &#x60;&#x60;&#x60;bash # 1. Token curl -X POST $API/ap1/token/ -H &#39;Content-Type: application/json&#39; \   -d &#39;{\&quot;client_id\&quot;: \&quot;&lt;client id&gt;\&quot;, \&quot;client_secret\&quot;: \&quot;&lt;client secret&gt;\&quot;}&#39; TOKEN&#x3D;&lt;access_token from the answer&gt;  # 2. Upload a PDF (answers with the upload&#39;s id) curl -X POST $API/ap1/upload/ -H \&quot;Authorization: Bearer $TOKEN\&quot; \   -F file&#x3D;@nda.pdf -F name&#x3D;nda-ann-lee -F type&#x3D;2  # 3. Find the document, then wait until it has pages curl \&quot;$API/ap1/documents/?name&#x3D;nda-ann-lee\&quot; -H \&quot;Authorization: Bearer $TOKEN\&quot; DOC&#x3D;&lt;results[0].id&gt; curl $API/ap1/documents/$DOC/pages/ -H \&quot;Authorization: Bearer $TOKEN\&quot; PAGE&#x3D;&lt;[0].id&gt;  # 4. Add the signatory curl -X POST $API/ap1/signatories/ -H \&quot;Authorization: Bearer $TOKEN\&quot; \   -H &#39;Content-Type: application/json&#39; \   -d &#39;{\&quot;document_id\&quot;: \&quot;&#39;$DOC&#39;\&quot;, \&quot;name\&quot;: \&quot;Ann Lee\&quot;, \&quot;email\&quot;: \&quot;ann@example.com\&quot;}&#39; SIGNER&#x3D;&lt;id from the answer&gt;  # 5. Place her signature: 150 × 40 points, 72 points from the left, 640 from the top curl -X POST $API/ap1/documents/fields/ -H \&quot;Authorization: Bearer $TOKEN\&quot; \   -H &#39;Content-Type: application/json&#39; \   -d &#39;[{\&quot;document_id\&quot;: \&quot;&#39;$DOC&#39;\&quot;, \&quot;signatory_id\&quot;: \&quot;&#39;$SIGNER&#39;\&quot;, \&quot;kind\&quot;: 2,         \&quot;label\&quot;: \&quot;Signature\&quot;, \&quot;placeholder\&quot;: \&quot;Sign here\&quot;, \&quot;page\&quot;: \&quot;&#39;$PAGE&#39;\&quot;,         \&quot;page_number\&quot;: 0, \&quot;x\&quot;: 72, \&quot;y\&quot;: 640, \&quot;width\&quot;: 150, \&quot;height\&quot;: 40}]&#39;  # 6. Send it (Ann gets the signing email) curl -X POST $API/ap1/document/send/ -H \&quot;Authorization: Bearer $TOKEN\&quot; \   -H &#39;Content-Type: application/json&#39; \   -d &#39;{\&quot;document_id\&quot;: \&quot;&#39;$DOC&#39;\&quot;, \&quot;subject\&quot;: \&quot;Please sign the NDA\&quot;,        \&quot;message\&quot;: \&quot;Hi Ann, please sign by Friday.\&quot;, \&quot;expiry_date\&quot;: \&quot;2026-12-31\&quot;}&#39;  # 7. Poll until status is 6 (Completed), then get the signed PDF curl \&quot;$API/ap1/documents-status-list/?document_ids&#x3D;$DOC\&quot; -H \&quot;Authorization: Bearer $TOKEN\&quot; curl \&quot;$API/ap1/document/download-link/?id&#x3D;$DOC\&quot; -H \&quot;Authorization: Bearer $TOKEN\&quot; &#x60;&#x60;&#x60;  ## Postman  Import the whole API into Postman with **Import → Link** and &#x60;&lt;this server&gt;/swaggerap1/postman.json&#x60;. Set &#x60;client_id&#x60; and &#x60;client_secret&#x60; in the collection&#39;s **Variables**, run **Get an access token** (it saves the token for every other request), and set &#x60;document_id&#x60; when a request needs one.  ## Webhooks  Instead of polling, add an endpoint with &#x60;POST /ap1/webhooks/&#x60; (or in **Account &amp; Settings → API**) and Signedly posts your organisation&#39;s document events to it: &#x60;document.sent&#x60;, &#x60;document.viewed&#x60;, &#x60;document.signed&#x60;, &#x60;document.approved&#x60;, &#x60;document.completed&#x60;, &#x60;document.rejected&#x60; and &#x60;document.expired&#x60; (all of them, or the &#x60;events&#x60; you choose). Each is a JSON &#x60;POST&#x60;:  &#x60;&#x60;&#x60;json {\&quot;id\&quot;: \&quot;5b7f…\&quot;, \&quot;type\&quot;: \&quot;document.signed\&quot;, \&quot;created_at\&quot;: \&quot;2026-10-27T09:12:00+00:00\&quot;,  \&quot;test\&quot;: false,  \&quot;data\&quot;: {\&quot;document\&quot;: {\&quot;id\&quot;: \&quot;6f0c…\&quot;, \&quot;name\&quot;: \&quot;nda.pdf\&quot;, \&quot;status\&quot;: 5},           \&quot;recipient\&quot;: {\&quot;id\&quot;: \&quot;0b6e…\&quot;, \&quot;email\&quot;: \&quot;ann@example.com\&quot;, \&quot;name\&quot;: \&quot;Ann Lee\&quot;,                         \&quot;role\&quot;: \&quot;signer\&quot;}}} &#x60;&#x60;&#x60;  &#x60;recipient&#x60; is null for events no recipient caused (sent, completed, expired); &#x60;test&#x60; is true for documents made with your test keys. Fetch anything else, such as download links, with your access token: events never carry links.  **Check every event is from Signedly.** The &#x60;Signedly-Signature&#x60; header is &#x60;t&#x3D;&lt;unix seconds&gt;,v1&#x3D;&lt;signature&gt;&#x60;, where the signature is the hex HMAC-SHA256 of &#x60;&lt;t&gt;.&lt;raw body&gt;&#x60; with the endpoint&#39;s secret. Refuse an event whose signature doesn&#39;t match or whose &#x60;t&#x60; is more than 5 minutes off:  &#x60;&#x60;&#x60;python import hashlib, hmac, time  def is_from_signedly(secret, header, raw_body):     parts &#x3D; dict(item.split(\&quot;&#x3D;\&quot;, 1) for item in header.split(\&quot;,\&quot;))     signed &#x3D; f\&quot;{parts[&#39;t&#39;]}.{raw_body.decode()}\&quot;.encode()     expected &#x3D; hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()     fresh &#x3D; abs(time.time() - int(parts[\&quot;t\&quot;])) &lt;&#x3D; 300     return fresh and hmac.compare_digest(parts[\&quot;v1\&quot;], expected) &#x60;&#x60;&#x60;  **Answer 2xx within 10 seconds**, then do the work. Anything else is retried after 1 and 5 minutes, then 30 minutes and 2, 5 and 10 hours (7 tries in about 18 hours). &#x60;Signedly-Event-Id&#x60; is the same on every try of an event: use it to ignore repeats. After 20 events in a row fail for good, Signedly switches the endpoint off and emails your organisation&#39;s admin; switch it on again with &#x60;PUT /ap1/webhooks/{id}/&#x60; and &#x60;\&quot;is_active\&quot;: true&#x60;. Each endpoint&#39;s deliveries and their last answers are at &#x60;GET /ap1/webhooks/{id}/deliveries/&#x60;, and any can be sent again.  ## Sandbox  Build and test against the API without paying for documents: in **Account &amp; Settings → API**, generate your **test keys**, and get a token with them at &#x60;/ap1/token/&#x60; as usual. Everything works the same, except that what you upload or make from a template with a test token is a **test document** (&#x60;is_test&#x60; on its upload):  - every page of its PDF and of its audit trail says **TEST – NOT LEGALLY BINDING**; - its emails are real, with &#x60;[TEST]&#x60; at the start of the subject; - it isn&#39;t billed or counted toward your plan; - your organisation can make 50 a calendar month (templates don&#39;t count); the next   upload answers 429.  ## Fields  Positions and sizes are in **PDF points** (1/72 inch), measured from the **top-left corner** of the page as it is displayed (rotation applied). &#x60;page_number&#x60; counts pages from 0. A field must sit wholly on its page (1 point of tolerance); otherwise it is refused with 400. A field&#39;s page and signatory can&#39;t be changed later: delete the field and add it again.  ## Status values  **Document &#x60;status&#x60;:** 1 Draft · 2 Signees Added · 3 Prepared · 4 Sent · 5 In Progress · 6 Completed · 7 Rejected · 8 Archived · 9 Expired. Signatories, fields and sends are only accepted while a document is 1–3 (not yet sent).  **Signatory &#x60;status&#x60;:** 1 Waiting · 2 Invited · 3 Signed · 4 Viewed · 5 Rejected.  ## Lists  &#x60;GET /ap1/documents/&#x60;, &#x60;/ap1/documents-status-list/&#x60;, &#x60;/ap1/templates/&#x60;, &#x60;/ap1/logs/&#x60; and &#x60;/ap1/logs/hits/&#x60; are paged: pass &#x60;limit&#x60; (default 10) and &#x60;offset&#x60;, and read &#x60;count&#x60;, &#x60;next&#x60;, &#x60;previous&#x60; and &#x60;results&#x60; from the response.  ## Errors  | Status | Meaning | |---|---| | 400 | The request was refused, also when your plan doesn&#39;t include it (see Plans); the body says why (&#x60;{\&quot;error\&quot;: \&quot;...\&quot;}&#x60;, or the name of each bad field with its messages). Nothing was changed. | | 403 | The access token is missing, unknown or expired; the client ID and secret or the refresh token were refused; the organisation&#39;s plan has no API access (Free, see Plans); or an &#x60;expiry_date&#x60; isn&#39;t in the future (&#x60;\&quot;Invalid Date Entered\&quot;&#x60;). | | 402 | No API contract, or an unpaid API invoice: live documents are refused (test keys keep working). | | 404 | The document, template, signatory or field doesn&#39;t exist or isn&#39;t yours. | | 429 | Too many token requests (wait a minute), or this month&#39;s 50 test documents are used. | | 5xx | Our side failed; retry later. |  A send that times out may still have worked: check the document&#39;s status before sending again (a second send of a sent document answers 400).  Download, view and audit links are valid for about 7 days; ask for a new one whenever you need it. 
    
