Postman collection¶
A ready-to-run Postman collection for the Lexgo Sign public API (/v1): 52 requests with tests and saved examples, three environments and three fictional PDF fixtures. It covers drafts with several documents, an attachment and several recipients (signer, approver, witness), envelope metadata, presigned uploads (small/medium/large) with a bounded wait for the worker, sending, 2FA validation codes, settings, webhooks and one optional OAuth Client API call.
Every request has a description with its effect and a pointer to the matching documentation page. Folders are numbered and tagged SAFE (unsent drafts with fictional data) or EFFECT (real emails, tenant-wide changes, outbound HTTP, real Alerts).
Files¶
| File | Purpose |
|---|---|
| LexgoSign.postman_collection.json | The collection (Postman v2.1): requests, tests, saved examples, guards |
| LexgoSign.local.postman_environment.json | Environment for a local development server |
| LexgoSign.staging.postman_environment.json | Environment for staging (api-beta.lexgo.cl) |
| LexgoSign.production.postman_environment.json | Environment for production (api.lexgo.cl) |
| fixtures/small.pdf | Fictional one-page PDF, about 1 KB |
| fixtures/medium.pdf | Fictional one-page PDF, about 200 KB |
| fixtures/large.pdf | Fictional one-page PDF, about 1 MB |
The PDFs contain only the text "Lexgo Sign sample - fictional"; the padding sits inside a valid PDF content stream. The application only accepts application/pdf. The same content as fixtures/small.pdf is kept as base64 in the collection variable sample_pdf_base64 for JSON and multipart creates.
Import¶
- In Postman choose Import and select the collection and the environment files (or drag them in).
- Open the environment you want, set
api_key(and any other variable you need, see below) in the Current value column, and select the environment. - Set the working directory: Settings > General > Working directory must point to the folder that contains
fixtures/(thispostmanfolder). The storage uploads in folder 2 use relative paths such asfixtures/small.pdf, which only resolve from there. - Run folders in order. Folder 1 saves the ids that later folders use.
Earlier drafts of this collection used base_url and client_prefix; they are replaced by api_url (host only) and app_url. A client environment that only has api_url (host) and api_key still works, because the collection defaults api_prefix to v1.
Generated state (envelope_id, presigned_url, presigned_url_id, webhook_id, ...) lives in collection variables. See Migrating an older environment if you already have an environment from an earlier collection.
Variable contract¶
Configuration variables are set in the environment files. Generated and state variables are defined at collection level and the collection writes them there. Scripts read every variable with pm.variables.get (which resolves local, data, environment, collection, then global) and write generated state with pm.collectionVariables.set.
| Variable | Defined in | Purpose |
|---|---|---|
api_url |
environment | Sign API host only: http://localhost:3000 (local), https://api-beta.lexgo.cl (staging), https://api.lexgo.cl (production). Requests are {{api_url}}/{{api_prefix}}/... |
api_prefix |
collection default v1; environment may override |
Path prefix: v1 on gateway hosts (staging, production), api/v1 for a local server (set in the local environment) |
api_key |
environment (secret, empty) | Sign API key, sent as the Authorization header value |
app_url |
environment | Web host of the OAuth Client API: http://localhost:3000, https://beta.lexgo.cl, https://app.lexgo.cl |
oauth_token |
environment (secret, empty) | Doorkeeper access token, only for folder 7 |
enterprise_id |
environment (empty) | Integer enterprise id sent as X-Enterprise-Id, only for folder 7 |
document_id |
environment (empty) | Integer id of an existing contract document, only for folder 7 |
effects_confirmed |
environment (no) |
Set to yes to let EFFECT folders run |
production_confirmed |
environment (no) |
Set to yes to let folders 1 to 7 run on a production host |
api_key_other_tenant |
environment (secret, empty, optional) | A different API key, used only for the tenant denial request |
fixture_signer_email |
environment | Signer address, default signer@example.com (this name was chosen over signer_email to line up with existing client environments) |
fixture_approver_email |
environment | Approver address, default approver@example.com; also the second signer in the two-signer drafts |
fixture_witness_email |
environment | Witness address, default witness@example.com |
sample_pdf_base64 |
collection | Base64 of fixtures/small.pdf for base64 creates |
webhook_url |
collection | Webhook target, default non-routable https://example.invalid/lexgo-sign-webhook |
validation_code |
collection | Code submitted by Verify validation code, default 000000 (expected to be rejected) |
envelope_id |
collection (generated) | Multipart draft created in folder 1 |
json_envelope_id |
collection (generated) | JSON draft with Unicode metadata created in folder 1 |
upload_envelope_id |
collection (generated) | Draft created for the upload walkthrough in folder 2 |
signer_id |
collection (generated) | Signer recipient id of envelope_id |
approver_id |
collection (generated) | Approver recipient id of envelope_id |
witness_id |
collection (generated) | Witness recipient id of envelope_id |
json_signer_id |
collection (generated) | Recipient id of json_envelope_id (changes after the update, which replaces recipients) |
metadata_sent |
collection (generated) | JSON text of the metadata that was sent, compared after the round trip |
presigned_url_id |
collection (generated) | Latest presigned upload id |
presigned_url |
collection (generated) | Latest presigned storage URL; cleared after the storage PUT because it is a credential |
webhook_id |
collection (generated) | Webhook created in folder 6 |
settings_backup_json |
collection (generated) | JSON text of the current settings values read in folder 5; the restore request sends it back |
zero_envelope_id |
collection (generated) | Draft created without files in folder 2 |
reminder_alert_date |
collection (generated) | Alert date (today plus 30 days) computed for folder 7 |
upload_poll_attempts |
collection (generated) | Attempt counter of the folder 2 completion poll; reset when the walkthrough draft is created and cleared when the poll ends |
storage_skipped |
collection (generated) | Set to yes when a presigned storage PUT was skipped (localhost); the folder 2 completion poll is then skipped too |
upload_poll_max |
collection | Maximum poll attempts, default 40 (capped at 100) |
upload_poll_delay_ms |
collection | Pause between poll attempts, default 3000 (capped at 3000) |
Resolution order: a variable set in a request's local scope or a data file wins over the environment, the environment wins over the collection, and the collection wins over globals. An environment, data file or --env-var that defines a generated state variable therefore would shadow the collection state. The collection refuses to run in that case: the collection-level pre-request script compares each generated variable with its collection value and, on a mismatch, makes the request unsendable and fails the run with an error that names the variable. It does the same when api_url has anything after the host.
Email addresses and sandbox keys¶
The defaults are fictional example.com addresses. A sandbox key only accepts recipient emails that belong to your own team; with the defaults, the create requests return 422 with envelope status ERROR and "not in the sandbox allowed email list". Set fixture_signer_email, fixture_approver_email and fixture_witness_email to team addresses (a live key accepts any address). For a bounded send smoke on staging use only the approved beta own-inbox aliases of the form contacto+<alias>@beta.lexgo.cl, never real external addresses.
Migrating an older environment¶
An environment from an earlier collection (for example one whose scripts use pm.environment.set for ids) can hold copies of the generated variables, and an old api_url that includes a path. Either fix works:
- Import a fresh environment from this folder and copy only your
api_key(and, if you use them,oauth_tokenandapi_key_other_tenant) into it, privately. Do not paste keys into files you commit. - Or edit the old environment: set
api_urlto the host only, with no path and no trailing slash (https://api.lexgo.cl,https://api-beta.lexgo.clorhttp://localhost:3000), and delete these variables from it, because the collection owns them: envelope_idjson_envelope_idupload_envelope_idzero_envelope_idsigner_idapprover_idwitness_idjson_signer_idmetadata_sentpresigned_url_idpresigned_urlwebhook_idsettings_backup_jsonreminder_alert_dateupload_poll_attemptsstorage_skipped
Keep api_key, api_url (host only) and, optionally, api_prefix, fixture_signer_email, fixture_approver_email and fixture_witness_email. Without api_prefix the collection uses v1, which is correct for the gateway hosts; a local server needs api_prefix set to api/v1.
If a shadowing variable or a api_url with a path is left behind, the first request of the run fails with an error that names it and nothing is sent.
Gateway and URLs¶
Rails serves the API under /api/v1. The API Gateway strips /api, so with api_url as the host and api_prefix as the path prefix the external URLs are:
- staging:
https://api-beta.lexgo.cl/v1(api_prefix=v1) - production:
https://api.lexgo.cl/v1(api_prefix=v1) - local development server:
http://localhost:3000/api/v1(api_prefix=api/v1, set in the local environment)
A 404 on /api/v1/... through a gateway host means the prefix was not stripped: use the documented form above, do not guess. The key stage (sandbox or live) belongs to the API key, not to the host. The OAuth Client API in folder 7 is different: it lives on the web host (app_url) under /api/client.
Safety map¶
| Folder | Tag | Requests | What it does |
|---|---|---|---|
0. SAFE - Connectivity |
SAFE | 3 | Liveness (no key), GET /test, POST /test. Only folder that runs on production without production_confirmed |
1. SAFE - Draft envelopes (unsent, fictional) |
SAFE | 20 | Auth denial (401 twice); create a draft by multipart (2 documents, 1 attachment, 3 recipients, SIGNATURE/APPROVER/WITNESS placements); create by JSON with nested Unicode metadata; get; round-trip check; update (recipients and placements; then documents and placements); six small role and file variants (1 signer/1 file with optional recipient fields and settings overrides, 1 signer/2 files with a token placement, 2 signers/2 files with numeric and custom recipient keys, 2 signers/2 files/1 attachment, approver + signer, witness + signer); scoped and full settings (read-only); tenant denial (404 with another key); three invalid-metadata cases (array, string, depth 6) |
2. SAFE - Uploads (draft only) |
SAFE | 15 | The supported presigned flow: a draft with a placement per file to be uploaded (document_key equals the presigned custom_key); presigned create (with custom_key), storage PUT and status for small, medium and large; presigned list; a bounded poll that waits until the worker has finished (4 documents, 4 linked placements, 3 uploads COMPLETED); a draft with 0 files followed by a presigned slot with file_name, order and custom_key |
3. EFFECT - Send and void (real emails/state) |
EFFECT | 3 | PUT /send emails recipients, PUT /void, GET /evidence |
4. EFFECT - 2FA validation codes (sends a code) |
EFFECT | 2 | send_code emails a code, verify_code |
5. EFFECT - Settings update (tenant-wide) |
EFFECT | 3 | Read (saves a backup), update branding, documents, emails, localization, signature and signature_flow, restore from the backup |
6. EFFECT - Webhooks (outbound HTTP to a URL) |
EFFECT | 5 | Create (with auth_type, auth and several subscriptions), list, get, update, delete a webhook pointing at webhook_url |
7. EFFECT - OAuth client reminders (optional, creates a real Alert) |
EFFECT | 1 | POST {{app_url}}/api/client/documents/{{document_id}}/reminders |
Guards:
- Every EFFECT folder has a folder-level pre-request script that calls
pm.execution.skipRequest()unlesseffects_confirmedisyes. - A collection-level pre-request script skips every request outside folder 0 when the host of
api_urlorapp_urlisapi.lexgo.clorapp.lexgo.cl, unlessproduction_confirmedisyes. Running the whole collection against production therefore only runs folder 0. - Skipped requests do not run their tests; the Postman console and newman log why.
Folder details¶
- Folder 1. The main multipart create saves
envelope_idand the recipient ids; the small variants only assert their own response. Optional recipient fields: the API reads the phone fromphone_id(andphone_label) and does not echo it back;tax_id,tax_label,rep_id,rep_labelare echoed. Atokenplacement has no coordinates: the token is searched as text in the PDF (the fixtures contain[[SIGN-HERE-1]]and[[SIGN-HERE-2]]). Update replaces all items of the kinds it sends (documents, recipients, placements); omitted kinds are kept andmetadatais ignored on update. Only SIGNATURE placements takecoordinates; APPROVER and WITNESS placements must not. The invalid-metadata requests are rejected with422 Invalid metadataand noenvelopein the body. That no database row is left behind is not provable from Postman; it is proved separately by a database count in the local proof. Metadata limits: 8192 bytes serialized, 5 levels (objects and arrays each count, the root is level 1), 100 keys. - Tenant denial. Envelopes belong to the API key that created them. Set
api_key_other_tenantto any different key (ideally of another enterprise); the request is skipped while it is empty. - Folder 2 (supported default). Presigned uploads allow 50 MB. Each storage
PUTis an outbound call to the storage host, sends the fixture as the raw body withContent-Type: application/pdf, uses request authnoauthand carries noAuthorizationheader (the collection has no collection-level auth, and a test asserts the header is absent). The presignedPUTrequests are skipped whenapi_urlpoints to localhost because the local dev server cannot complete a real storage upload. A draft created without files is answered with422and statusERROR("no PDF files"), but it still accepts presigned slots; the slot'scustom_keymatches the placement'sdocument_key. The upload walkthrough draft works the same way: it has one placement per file to upload (document_keyfixture-small,fixture-medium,fixture-large) and is therefore answered with422/ERRORuntil the uploads complete; the worker then links each placement to the uploaded file whosecustom_key(or id, or numeric position) equals the placement'sdocument_key. Placement storage is verified on a local server; the linking after a real storage upload is verified only by the poll when the PUTs actually run. - Pending, completed, timeout. Right after the PUTs the uploads are
PENDINGorUPLOADEDand the draft is stillERROR(placements point at files that are not there yet). An asynchronous worker then creates the files and links the placements. The request "Wait for uploads to complete" reads the draft and its uploads and repeats itself, at mostupload_poll_maxtimes (40 by default) about 3 seconds apart, until the draft isCREATEDwithout errors, has 4 documents and 4 placements that are all linked to a document, and 3 uploads areCOMPLETED. It fails at once on aFAILEDorEXPIREDupload, and after the last attempt it says whether uploads are still pending (a PUT or the worker did not finish) or relationships are missing.setNextRequestrepeats only in the Collection Runner and newman; in a manual single Send, press Send again until the assertions pass. Before a manual Send in folder 3 (EFFECT), wait until the poll passes. On localhost the PUTs are skipped (storage_skippedis set toyes) and the poll is skipped too. The uploads list and the presigned list return the same records for an envelope. - Legacy streaming uploads are retired. The older Lambda and API Gateway upload path (
POST /envelopes/{id}/uploadsand aPUTto theupload_urlit returns) is not part of this collection. The owner decision in issue #1207 retains presigned URLs and retires that path; its returnedupload_urlalso points at a legacy host that does not resolve on beta. The presigned flow in folder 2 is the only supported upload flow. The old request shapes are kept only as a retirement reference in the repository filedocs/archive/postman/legacy-lambda-streaming-uploads.md(not runnable, not importable). - Folder 3. Send emails real recipients. Void only works for
IN_PROGRESSenvelopes, so right after Send a405is expected and accepted by the tests; evidence needs aSUCCESSenvelope. - Folder 5. Changes configuration for the whole API key. Run the three requests in order; the read request clears any earlier backup and saves the current values of every field the update touches and the last request sends them back. Update and Restore are skipped unless a complete backup exists, and Restore builds its JSON body in a script so quotes, backslashes, newlines and double-brace text in a value stay intact. The update uses the documented default values, so it only changes tenants that customised them.
- Folder 7. Optional and isolated. It uses the OAuth Client API, not the Sign API: a Doorkeeper access token (scope
client) inoauth_token, the integerenterprise_idinX-Enterprise-Id, and an enterprise permission on the document. It creates a real Alert and reminder; the email is sent on the fire date (alert date minus 3 days). It is skipped whileoauth_token,enterprise_idordocument_idis empty.
Usage-report export is an enterprise Settings UI action (see Usage reports); there is no API or Postman request for it.
Running with newman¶
Install newman (npm install -g newman) and run from this folder. Pass secrets without writing them into files that you commit, for example with --env-var "api_key=$LEXGO_API_KEY".
Safe folders against a local development server:
cd api/docs/postman
newman run LexgoSign.postman_collection.json \
-e LexgoSign.local.postman_environment.json \
--env-var "api_key=$LEXGO_API_KEY" \
--working-dir . \
--folder "0. SAFE - Connectivity" \
--folder "1. SAFE - Draft envelopes (unsent, fictional)" \
--folder "2. SAFE - Uploads (draft only)"
Staging uses the same command with LexgoSign.staging.postman_environment.json. Production safe check (folder 0 only):
newman run LexgoSign.postman_collection.json \
-e LexgoSign.production.postman_environment.json \
--env-var "api_key=$LEXGO_API_KEY" \
--folder "0. SAFE - Connectivity"
To run an EFFECT folder you must opt in explicitly, for example --env-var effects_confirmed=yes --folder "3. EFFECT - Send and void (real emails/state)". On production folders 1 to 7 additionally need --env-var production_confirmed=yes.
Working directory and file access: --working-dir . (the default is the current directory) makes fixtures/small.pdf resolve. Files inside the working directory are read without extra flags; --insecure-file-read is only needed for paths outside it, which this bundle never uses.
Throttling: the API rejects more than 60 requests per minute from one IP address; do not run several newman processes in parallel.
Known limits¶
- The real storage
PUTand the completion poll are not exercised against real storage and the worker by the local proof; those requests are skipped on localhost and were checked against a local stand-in server for headers, file bodies and polling behaviour. - EFFECT folders are not run in CI or in the local proof. Their tests and examples follow the controller behavior and the public docs.
- Drafts created by the SAFE folders remain in the account; the API has no delete endpoint for envelopes.
- Base64 documents in create and update requests are limited to 5 MB decoded; use uploads for larger files.
POST /webhookswith an unsupportedauth_typecurrently answers500instead of a validation error, so the collection has no negative webhook request.- Webhook delivery bodies and 2FA email contents are not asserted; check them in your own endpoint and inbox.
- The OAuth Client API base on the gateway (
/client) is not verified; the collection uses the web hostapp_url.
Maintenance¶
Before promoting any legacy route or request shape from an old PR, an old collection or the source back into this collection, check the owner decisions (for uploads, issue #1207) and the runtime. Retired shapes are kept only as a retirement reference, never as active requests.