diff --git a/evals/evals.json b/evals/evals.json index f98be75..5d27750 100644 --- a/evals/evals.json +++ b/evals/evals.json @@ -3568,6 +3568,179 @@ "description": ".env file contains TURBODOCX_API_KEY and TURBODOCX_ORG_ID" } ] + }, + { + "id": 72, + "prompt": "Our Express (TypeScript) app frames TurboSign in a narrow side panel. Add an endpoint that creates the signature request for one signer and returns the embed URL, and hide the page thumbnails / Pages button inside the iframe because the panel is too small for them.", + "expected_output": "Endpoint calling TurboSign.createEmbeddedSignature with pageNavigation: 'hidden' (typed PageNavigationMode if typed at all) and returning recipients[].embedUrl. No string other than 'auto' | 'collapsed' | 'hidden'; no invented prop on the browser widget.", + "files": [ + "package.json", + "tsconfig.json", + "src/index.ts", + "package-lock.json" + ], + "assertions": [ + { + "name": "uses-createEmbeddedSignature", + "type": "file_contains", + "description": "Server calls TurboSign.createEmbeddedSignature" + }, + { + "name": "page-navigation-hidden", + "type": "file_contains", + "description": "The createEmbeddedSignature request passes pageNavigation: 'hidden' (camelCase key, string value 'hidden'), not a made-up key like hideThumbnails / showPages / navigation" + }, + { + "name": "no-widget-prop", + "type": "file_not_contains", + "description": "Code does NOT try to hide the navigation with a prop or query parameter on the browser widget or the embed URL; the setting is passed server-side when the URL is minted" + }, + { + "name": "surfaces-embed-url", + "type": "file_contains", + "description": "Response returns the recipient's embedUrl for the app to frame" + } + ] + }, + { + "id": 73, + "prompt": "In my FastAPI app we already send documents with send_signature and our own external_id. Add an endpoint that mints the embedded signing URL when the customer clicks Sign, and start the page thumbnail rail collapsed in the iframe so the document gets more room.", + "expected_output": "Endpoint awaiting TurboSign.create_signing_url(document_id, external_id=..., page_navigation=\"collapsed\") and returning the url.", + "files": [ + "pyproject.toml", + "app/main.py", + "app/routers/__init__.py" + ], + "assertions": [ + { + "name": "uses-create-signing-url", + "type": "file_contains", + "description": "Endpoint awaits TurboSign.create_signing_url with exactly one selector (external_id)" + }, + { + "name": "page-navigation-collapsed-snake", + "type": "file_contains", + "description": "The call passes page_navigation=\"collapsed\" (snake_case keyword), NOT pageNavigation= as a kwarg" + }, + { + "name": "returns-url", + "type": "file_contains", + "description": "Response returns the url field from the create_signing_url response" + } + ] + }, + { + "id": 74, + "prompt": "Go net/http service: mint an embedded TurboSign signing URL for a recipient by our customer ID when they click Sign, and hide the page navigation rail in the iframe. Two signers sign in order, so the second signer's URL is minted later the same way.", + "expected_output": "CreateSigningURL with ExternalID and PageNavigation: turbodocx.PageNavigationHidden on every mint (including the later second signer), error checked.", + "files": [ + "go.mod", + "cmd/server/main.go", + "internal/handlers/.gitkeep" + ], + "assertions": [ + { + "name": "page-navigation-constant", + "type": "file_contains", + "description": "CreateSigningURLRequest sets PageNavigation: turbodocx.PageNavigationHidden (the typed constant; a raw \"hidden\" string literal converted to turbodocx.PageNavigationMode is also acceptable, an invented field name is not)" + }, + { + "name": "every-mint-sets-it", + "type": "file_contains", + "description": "Every CreateSigningURL call in the generated code (including the one used for the second signer) sets PageNavigation, since the setting applies per URL and an omitted value resets to auto" + }, + { + "name": "handles-error-return", + "type": "file_contains", + "description": "The (result, error) return of CreateSigningURL is checked" + } + ] + }, + { + "id": 75, + "prompt": "Laravel app: add a controller action that creates an embedded TurboSign signature request for a single signer with email OTP and returns the embed URL. Our iframe is small, so the page thumbnail rail should start collapsed.", + "expected_output": "TurboSign::createEmbeddedSignature(new CreateEmbeddedSignatureRequest(..., pageNavigation: PageNavigation::COLLAPSED)) with use TurboDocx\\Types\\PageNavigation; returns the embedUrl.", + "files": [ + "composer.json", + "routes/api.php", + "routes/web.php", + "app/Http/Controllers/Controller.php", + "app/Providers/AppServiceProvider.php", + "bootstrap/app.php" + ], + "assertions": [ + { + "name": "uses-createEmbeddedSignature", + "type": "file_contains", + "description": "Controller calls TurboSign::createEmbeddedSignature with a CreateEmbeddedSignatureRequest" + }, + { + "name": "page-navigation-enum", + "type": "file_contains", + "description": "The request passes pageNavigation: PageNavigation::COLLAPSED and imports TurboDocx\\Types\\PageNavigation; it does NOT pass the string 'collapsed' to the typed constructor (a TypeError under strict types)" + }, + { + "name": "email-otp", + "type": "file_contains", + "description": "The recipient sets auth: new EmbeddedRecipientAuth(emailOtp: true)" + } + ] + }, + { + "id": 76, + "prompt": "Spring Boot service: add an endpoint that mints an embedded TurboSign signing URL for an existing document by recipient ID, with no page thumbnail navigation at all inside our iframe.", + "expected_output": "createSigningUrl(documentId, new CreateSigningUrlRequest.Builder().recipientId(...).pageNavigation(PageNavigation.HIDDEN).build()) with PageNavigation imported from com.turbodocx.models.", + "files": [ + "pom.xml", + "src/main/java/com/example/app/Application.java", + "src/main/java/com/example/app/HealthController.java", + "src/main/resources/application.properties" + ], + "assertions": [ + { + "name": "uses-create-signing-url", + "type": "file_contains", + "description": "Endpoint calls client.turboSign().createSigningUrl with a CreateSigningUrlRequest.Builder selecting by recipientId" + }, + { + "name": "page-navigation-builder", + "type": "file_contains", + "description": "The builder calls .pageNavigation(PageNavigation.HIDDEN) using the com.turbodocx.models.PageNavigation enum (imported directly or via com.turbodocx.models.*), not a string" + }, + { + "name": "returns-url", + "type": "file_contains", + "description": "Response returns link.getUrl()" + } + ] + }, + { + "id": 77, + "prompt": "Rails app: add a controller action that mints the embedded TurboSign signing URL for a recipient by our external id when they click Sign, and hide the page navigation rail inside the iframe.", + "expected_output": "TurboDocxSdk::TurboSign.create_signing_url(document_id, external_id: ..., page_navigation: \"hidden\") returning link[\"url\"].", + "files": [ + "Gemfile", + "config/application.rb", + "config/routes.rb", + "app/controllers/application_controller.rb" + ], + "assertions": [ + { + "name": "uses-create-signing-url", + "type": "file_contains", + "description": "Action calls TurboDocxSdk::TurboSign.create_signing_url with external_id:" + }, + { + "name": "page-navigation-keyword", + "type": "file_contains", + "description": "The call passes the snake_case keyword page_navigation: \"hidden\" (create_signing_url does not accept pageNavigation: as a keyword)" + }, + { + "name": "route-wired", + "type": "file_contains", + "description": "config/routes.rb was modified to add the new route" + } + ] } ] } diff --git a/skills/turbodocx-sdk/SKILL.md b/skills/turbodocx-sdk/SKILL.md index 342de8b..bd206a9 100644 --- a/skills/turbodocx-sdk/SKILL.md +++ b/skills/turbodocx-sdk/SKILL.md @@ -203,6 +203,7 @@ Create working route handlers / endpoint code for the selected product(s). The l - **A locked channel fails the send.** When `allowChannelOverride` is false, an explicit channel other than `defaultChannel` is rejected with HTTP 403 code `OtpOverrideNotAllowed` (the Authorization error class; branch on its code, and in PHP read `$e->errorCode`). Always read it before forcing a per-recipient channel, and read it null-safely: only an explicit false means locked (JS `settings.allowChannelOverride === false`), a missing value means unknown. Do this even when the default is `none` today: an admin can change the org default and lock it at any time, so checking `defaultChannel !== 'none'` alone is not enough. - **Embedded sends use `sendEmail: false`** (the default on `createEmbeddedSignature`; pass it yourself on `sendSignature`) and say why in a comment: no signing-link, "your turn", reminder or expiry-warning emails, while passcode and completed-copy emails still go out. - **`external_idv` assertions** can carry optional audit-trail context (`method`, `methodDetail`, `assuranceLevel`, `verifiedName`, `evidenceUrl`, `overrideEmailMatching`) in every language; use the casing the language reference shows. + - **Page navigation in the iframe is optional.** Only when the user asks to hide or collapse the signing page's page thumbnails (the thumbnail rail / "Pages" button), pass `pageNavigation` (`'auto'` default, `'collapsed'`, `'hidden'`) to `createSigningUrl()` / `createEmbeddedSignature()`, with the per-language name and type from the reference (Python `page_navigation=`, Ruby `page_navigation:` on `create_signing_url`, Go `PageNavigation: turbodocx.PageNavigationHidden`, PHP `PageNavigation::HIDDEN`, Java `.pageNavigation(PageNavigation.HIDDEN)`). It applies to that one URL, so pass it again on every re-mint. Don't set it by default. - **`expiresAt`** is 5 minutes after issue for single-use `external_idv` / `override` links; for `otp` / no-verification links it is the document's own expiry, or null when the document doesn't expire. The API key stays **server-side**; the browser frames the returned `/e-signature/embed/...` URL and never calls the SDK. On the browser side prefer `@turbodocx/embed` (`` / ``): its `onCompleted` receives `event` (`signing_complete` or `already_signed`, so skip one-time side effects on the latter) and `scope` (`recipient`: this signer only, so read whole-document status server-side). The canonical end-to-end example is the embedding web app at `examples/embedded-web-app` (a React SPA to BFF to SDK reference with single-signer, external IdV, sequential kiosk, and `` widget paths); copy it as the starting point. Embedded signing is **default-deny**: the org must allow-list your app's origin under E-Signature settings before the iframe will render, and an empty allow-list denies framing everywhere. diff --git a/skills/turbodocx-sdk/references/go.md b/skills/turbodocx-sdk/references/go.md index ea9b70a..1d00cae 100644 --- a/skills/turbodocx-sdk/references/go.md +++ b/skills/turbodocx-sdk/references/go.md @@ -526,6 +526,7 @@ documentID := sent.DocumentID link, err := client.TurboSign.CreateSigningURL(ctx, documentID, &turbodocx.CreateSigningURLRequest{ ExternalID: "your_customer_123", // XOR RecipientID ReturnURL: "https://app.yourcompany.com/signed", // optional, https only + // PageNavigation: turbodocx.PageNavigationCollapsed, // optional: page thumbnails in the iframe: Auto (default) | Collapsed | Hidden }) if err != nil { log.Fatal(err) @@ -563,6 +564,7 @@ Returns `*turbodocx.CreateSigningURLResponse` — `URL`, `ExpiresAt` (`*string`) - **`IdentityAssertion` is only for `external_idv` recipients.** Sending it for an OTP, override, or no-verification recipient is rejected with a 400 `IdentityModeConflict` error, so pass it only when the recipient is `external_idv`. `Provider`, `VerificationID`, `VerifiedAt`, `SubjectEmail` are required; `Method`, `MethodDetail` (required when `Method` is `"other"`), `AssuranceLevel`, `VerifiedName`, `EvidenceURL` (https) and `OverrideEmailMatching` (`bool`) are optional and left off the wire when empty. - **Request at click time, never store the URL.** For the bypass modes (`external_idv` / `override`) the link is single-use and expires 5 minutes after issue (a `?sut=` redeem-once URL), so mint a fresh one each time. For `otp` and no-verification recipients `URL` is the reusable signing link (a `?token=` URL), so `ExpiresAt` is the **document's own expiry**, or nil when the document doesn't expire. - **`ReturnURL` must be https**, or the SDK returns a `*turbodocx.ValidationError` with code `InvalidReturnUrl`. +- **`PageNavigation` is optional; leave it out unless the user asks to change the page navigation.** It is a `turbodocx.PageNavigationMode` that sets how the signing page shows its page thumbnails inside your iframe: `turbodocx.PageNavigationAuto` (`"auto"`, the default) sizes it to the iframe width (the thumbnail rail is open at 1200px and wider, collapsed from 900px to 1199px, and a "Pages" button in the bottom bar under 900px), `turbodocx.PageNavigationCollapsed` starts the rail collapsed at any width (the signer can still open it), and `turbodocx.PageNavigationHidden` shows no rail and no Pages button. Empty sends nothing. It applies to that URL only: a later `CreateSigningURL` call without it resets to auto, so set it on every mint. It has no effect when the URL is opened outside an iframe. ### CreateEmbeddedSignature @@ -584,6 +586,7 @@ resp, err := client.TurboSign.CreateEmbeddedSignature(ctx, &turbodocx.CreateEmbe }, }, ReturnURL: "https://app.yourcompany.com/signed", // optional, https, passed to each embed URL + // PageNavigation: turbodocx.PageNavigationHidden, // optional, passed to each embed URL: Auto (default) | Collapsed | Hidden // SendEmail nil already means false here: no signing-link, reminder or expiry-warning emails. }) var authz *turbodocx.AuthorizationError @@ -604,6 +607,7 @@ for _, r := range resp.Recipients { Returns `*turbodocx.CreateEmbeddedSignatureResponse`: `DocumentID` plus `Recipients` (`[]turbodocx.EmbeddedSignatureRecipientResult`), one result per signer **in signing order**, each carrying `RecipientID`, `Name`, `Email`, `EmbedURL`, `Status`, `IdentityVerificationMode`. For a `"ready"` signer that mode is what the backend resolved for the URL; for `"pending"` / `"completed"` no URL was minted, so it is the mode you **requested** via `Auth` (`""` when you set none, even if the org's default channel will apply). `CreateSigningURL` reports the effective mode. - **Turn-aware `Status`.** `SigningOrder` defaults to each recipient's index + 1, so a two-signer call is sequential: only signer 1 comes back `"ready"` with an `EmbedURL`, and the rest are `"pending"` (an earlier signer has not finished, `EmbedURL` empty) or `"completed"` (already signed). Re-mint the next signer's URL with `CreateSigningURL` once earlier signers finish (the just-in-time kiosk pattern). Give every recipient the same `SigningOrder` if you want them all `"ready"` at once. +- **`PageNavigation` is passed to each embed URL, like `ReturnURL`.** It is not saved on the document: when you re-mint a `"pending"` signer's URL with `CreateSigningURL`, set the same `PageNavigation` again or that signer gets the default auto navigation. - **`Auth` is OTP-only.** `EmailOTP: true` maps to `{Mode: "otp", Channel: "email"}`; `SMS: &EmbeddedRecipientSMS{PhoneNumber: "+13055551234"}` maps to `{Mode: "otp", Channel: "sms"}` and sets the recipient's `Phone`. `EmailOTP` wins if both are set. No `Auth` means the org's `DefaultChannel` applies; an `Auth` channel that differs from a locked default fails the whole call with `OtpOverrideNotAllowed`. An SMS recipient with no resolvable phone returns a `*turbodocx.ValidationError` with code `PhoneRequiredForSmsOtp`. For `external_idv` / `override`, use `SendSignature` + `CreateSigningURL` instead. - **The `Fields` shorthand anchors by text.** Each key (`Signature`, `Date`, `Initials`, `FullName`) takes the anchor text to replace, expanded to a full field with `Placement: "replace"` and a default size. Note `Initials` emits the field type `"initial"` (there is no `"initials"` type). Pass a top-level `Fields []turbodocx.Field` for full control; it **replaces** the per-recipient shorthand, it does not merge with it. An empty top-level `Fields` counts as absent, so the shorthand still expands (0.8.0 sent the empty list and dropped it). - **`SendEmail` defaults to `false`** for this flow: the signing-link emails, the initial CC notice, the next signer's "your turn" email and the scheduled reminder and expiry-warning emails are suppressed, while passcode and completed-copy emails are still sent. Set `SendEmail` to a `*bool` to override. diff --git a/skills/turbodocx-sdk/references/java.md b/skills/turbodocx-sdk/references/java.md index af49bc7..271e373 100644 --- a/skills/turbodocx-sdk/references/java.md +++ b/skills/turbodocx-sdk/references/java.md @@ -470,6 +470,7 @@ CreateSigningUrlResponse link = client.turboSign().createSigningUrl( new CreateSigningUrlRequest.Builder() .externalId("your_customer_123") // XOR .recipientId("recipient-uuid") .returnUrl("https://app.yourcompany.com/signed") // optional, https only + // .pageNavigation(PageNavigation.COLLAPSED) // optional: page thumbnails in the iframe: AUTO (default) | COLLAPSED | HIDDEN .build() ); @@ -505,6 +506,8 @@ CreateSigningUrlResponse link = client.turboSign().createSigningUrl( Request it **at click time**. For the bypass modes (`external_idv` / `override`) the URL is **single-use** and expires 5 minutes after issue (a `?sut=` link redeemed once when opened), so mint a fresh one each time. For no-verification and `otp` recipients the URL is the reusable signing link (`?token=`) that survives a refresh, and `getExpiresAt()` is the **document's own expiry**, or `null` when the document doesn't expire. +**`.pageNavigation(...)` is optional; leave it out unless the user asks to change the page navigation.** It takes the `com.turbodocx.models.PageNavigation` enum and sets how the signing page shows its page thumbnails inside your iframe: `PageNavigation.AUTO` (the default) sizes it to the iframe width (the thumbnail rail is open at 1200px and wider, collapsed from 900px to 1199px, and a "Pages" button in the bottom bar under 900px), `PageNavigation.COLLAPSED` starts the rail collapsed at any width (the signer can still open it), and `PageNavigation.HIDDEN` shows no rail and no Pages button. Null sends nothing. It applies to that URL only: a later `createSigningUrl` call without it resets to `AUTO`, so set it on every mint. It has no effect when the URL is opened outside an iframe. + ### getEmbeddedSigningSettings Read-only org gates — check what your org allows before you request signing URLs. Change these in E-Signature settings (Identity & embedding tab) or via the organization preferences API, where the change is recorded in the settings audit trail. @@ -555,6 +558,7 @@ try { .build() )) .returnUrl("https://app.yourcompany.com/signed") // optional, passed to each embed URL + // .pageNavigation(PageNavigation.HIDDEN) // optional, passed to each embed URL: AUTO (default) | COLLAPSED | HIDDEN .build() ); } catch (TurboDocxException.AuthorizationException e) { @@ -571,7 +575,7 @@ for (EmbeddedSignatureRecipientResult r : embedded.getRecipients()) { } ``` -`sendEmail` defaults to `false` for this flow: the signing-link emails, the initial CC notice, the next signer's "your turn" email and the scheduled reminder and expiry-warning emails are suppressed, while passcode and completed-copy emails are still sent. No `auth` means the org's `defaultChannel` applies; an `auth` channel that differs from a locked default fails the whole call with `OtpOverrideNotAllowed`. For a `ready` signer `getIdentityVerificationMode()` is the mode the backend resolved; for `pending` / `completed` no URL was minted, so it is the mode you **requested** via `auth` (`null` when you set none, even if the org's default channel will apply). Pass a top-level `.fields(List)` to override every recipient's `fields` shorthand verbatim; an empty list counts as absent, so the shorthand still expands (0.8.0 sent the empty list and dropped it). It is **turn-aware**: with a real signing order the backend mints a URL only for the signer whose turn it is: a later signer comes back `pending` with a null `embedUrl`, so re-mint with `createSigningUrl` once earlier signers finish (e.g. an in-person kiosk handing the device to the next signer). A genuine error (any code other than not-in-turn / already-signed) still throws. +`sendEmail` defaults to `false` for this flow: the signing-link emails, the initial CC notice, the next signer's "your turn" email and the scheduled reminder and expiry-warning emails are suppressed, while passcode and completed-copy emails are still sent. No `auth` means the org's `defaultChannel` applies; an `auth` channel that differs from a locked default fails the whole call with `OtpOverrideNotAllowed`. For a `ready` signer `getIdentityVerificationMode()` is the mode the backend resolved; for `pending` / `completed` no URL was minted, so it is the mode you **requested** via `auth` (`null` when you set none, even if the org's default channel will apply). Pass a top-level `.fields(List)` to override every recipient's `fields` shorthand verbatim; an empty list counts as absent, so the shorthand still expands (0.8.0 sent the empty list and dropped it). It is **turn-aware**: with a real signing order the backend mints a URL only for the signer whose turn it is: a later signer comes back `pending` with a null `embedUrl`, so re-mint with `createSigningUrl` once earlier signers finish (e.g. an in-person kiosk handing the device to the next signer). `.pageNavigation(...)` is passed to each embed URL like `.returnUrl(...)`, but it is not saved on the document: set it again on the `CreateSigningUrlRequest` when you re-mint a `pending` signer, or that signer gets the default `AUTO` navigation. A genuine error (any code other than not-in-turn / already-signed) still throws. ### Identity verification modes diff --git a/skills/turbodocx-sdk/references/javascript.md b/skills/turbodocx-sdk/references/javascript.md index 18dbd53..e334cba 100644 --- a/skills/turbodocx-sdk/references/javascript.md +++ b/skills/turbodocx-sdk/references/javascript.md @@ -457,6 +457,7 @@ const { documentId } = await TurboSign.sendSignature({ const link = await TurboSign.createSigningUrl(documentId, { externalId: 'your_customer_123', // XOR recipientId returnUrl: 'https://app.yourcompany.com/signed', // optional, https only + // pageNavigation: 'collapsed', // optional: page thumbnails in the iframe: 'auto' (default) | 'collapsed' | 'hidden' }); console.log(link.url); // open / redirect / iframe this @@ -489,6 +490,7 @@ Response: `{ url, expiresAt, recipientId, externalId?, identityVerificationMode, - **`identityAssertion` is only for `external_idv` recipients.** Sending it for an OTP, override, or no-verification recipient is rejected with a 400 `IdentityModeConflict` error, so pass it only when the recipient is `external_idv`. The four keys `provider`, `verificationId`, `verifiedAt`, `subjectEmail` are required; `method`, `methodDetail` (required when `method` is `'other'`), `assuranceLevel`, `verifiedName`, `evidenceUrl` (https) and `overrideEmailMatching` are optional audit-trail context and are left off the wire when unset. - **Request at click time, never store the URL.** For the bypass modes (`external_idv` / `override`) the link is single-use and expires 5 minutes after issue (a `?sut=` redeem-once URL), so mint a fresh one each time. For `otp` and no-verification recipients `url` is the reusable signing link (a `?token=` URL), so `expiresAt` is the **document's own expiry**, or `null` when the document doesn't expire. - **`returnUrl` must be https**, or the SDK throws `ValidationError` with code `InvalidReturnUrl`. +- **`pageNavigation` is optional; leave it out unless the user asks to change the page navigation.** It sets how the signing page shows its page thumbnails inside your iframe (type `PageNavigationMode`): `'auto'` (the default) sizes it to the iframe width (the thumbnail rail is open at 1200px and wider, collapsed from 900px to 1199px, and a "Pages" button in the bottom bar under 900px), `'collapsed'` starts the rail collapsed at any width (the signer can still open it), and `'hidden'` shows no rail and no Pages button. It applies to that URL only: a later `createSigningUrl` call without it resets to `'auto'`, so pass it on every mint. It has no effect when the URL is opened outside an iframe. ### TurboSign.createEmbeddedSignature @@ -520,6 +522,7 @@ try { }, ], returnUrl: 'https://app.yourcompany.com/signed', // optional, https, passed to each embed URL + // pageNavigation: 'hidden', // optional, passed to each embed URL: 'auto' (default) | 'collapsed' | 'hidden' }); for (const r of recipients) { @@ -537,6 +540,7 @@ try { Response: `{ documentId, recipients: EmbeddedSignatureRecipientResult[] }`, one result per signer **in signing order**, each `{ recipientId, name, email, embedUrl, status, identityVerificationMode }`. For a `'ready'` signer `identityVerificationMode` is the mode the backend resolved for the URL. For `'pending'` / `'completed'` no URL was minted, so it is the mode you **requested** via `auth`: `null` when you set none, even if the org's default channel will apply. `createSigningUrl` reports the effective mode once you mint the URL. - **Turn-aware `status`.** `signingOrder` defaults to each recipient's array index + 1, so a two-signer call is sequential: only signer 1 comes back `'ready'` with an `embedUrl`, and the rest are `'pending'` with `embedUrl: null` (an earlier signer has not finished) or `'completed'` (already signed). Re-mint the next signer's URL with `createSigningUrl` once earlier signers finish (the just-in-time kiosk pattern). Give every recipient the same `signingOrder` if you want them all `'ready'` at once. +- **`pageNavigation` is passed to each embed URL, like `returnUrl`.** It is not saved on the document: when you re-mint a `'pending'` signer's URL with `createSigningUrl`, pass the same `pageNavigation` again or that signer gets the default `'auto'` navigation. - **`auth` is OTP-only.** `{ emailOtp: true }` maps to `{ mode: 'otp', channel: 'email' }`; `{ sms: { phoneNumber: '+13055551234' } }` maps to `{ mode: 'otp', channel: 'sms' }` and sets the recipient's `phone`. No `auth` means the org's `defaultChannel` applies. An `auth` channel that differs from a locked default (`allowChannelOverride === false`) makes the whole call fail with `AuthorizationError` code `OtpOverrideNotAllowed`. For `external_idv` / `override`, use `sendSignature` + `createSigningUrl` instead. - **The `fields` shorthand anchors by text.** Each key (`signature`, `date`, `initials`, `fullName`) takes the anchor text to replace, expanded to a full field with `placement: 'replace'` and a default size. Note `initials` emits the field type `'initial'` (there is no `'initials'` type). Pass a top-level `fields: Field[]` for full control; it **replaces** the per-recipient shorthand, it does not merge with it. An empty top-level `fields` counts as absent, so the shorthand still expands (0.8.0 sent the empty list and dropped it). - **`sendEmail` defaults to `false`** for this flow: the signing-link emails, the initial CC notice, the next signer's "your turn" email and the scheduled reminder and expiry-warning emails are suppressed. Passcode and completed-copy emails are still sent. diff --git a/skills/turbodocx-sdk/references/php.md b/skills/turbodocx-sdk/references/php.md index a616312..6ae5be9 100644 --- a/skills/turbodocx-sdk/references/php.md +++ b/skills/turbodocx-sdk/references/php.md @@ -491,6 +491,7 @@ use TurboDocx\Types\TemplateConfig; use TurboDocx\Types\FieldPlacement; use TurboDocx\Types\Requests\SendSignatureRequest; use TurboDocx\Types\Requests\CreateSigningUrlRequest; +// use TurboDocx\Types\PageNavigation; // only if you set pageNavigation below // 1) Prepare the document. No identityVerification here = the org's defaultChannel applies // ('none' = no step-up). @@ -526,6 +527,7 @@ $sent = TurboSign::sendSignature(new SendSignatureRequest( $link = TurboSign::createSigningUrl($sent->documentId, new CreateSigningUrlRequest( externalId: 'your_customer_123', returnUrl: 'https://app.yourcompany.com/signed', // optional, https only + // pageNavigation: PageNavigation::COLLAPSED, // optional: page thumbnails in the iframe: AUTO (default) | COLLAPSED | HIDDEN )); echo "Open for the signer: {$link->url}\n"; @@ -539,6 +541,7 @@ echo "Expires: " . ($link->expiresAt ?? '(none; the document does not expire)') - **Exactly one selector** — pass `recipientId` **or** `externalId`, never both and never neither, or the SDK throws `ValidationException` (`RecipientSelectorInvalid`) before any HTTP call. `returnUrl`, when present, must be `https://`. - **Request at click time, never store.** For the **bypass modes** (`external_idv`, `override`) the URL is **single-use** and expires 5 minutes after issue (a `?sut=` link the page redeems once); mint a fresh one each time. For `otp` / no-verification the `url` is the reusable `?token=` signing link, so `expiresAt` is the **document's own expiry**, or `null` when the document doesn't expire. - `pendingChecks` lists the passcode step the signer clears on the page (`['email_otp']` / `['sms_otp']`); it is non-empty **only for `otp`** (including an `otp` the org default applied). With no verification, external_idv, or override it is `[]`. +- **`pageNavigation` is optional; leave it out unless the user asks to change the page navigation.** It takes the `TurboDocx\Types\PageNavigation` enum (pass the case, not a string: the constructor is typed `?PageNavigation`, so `'hidden'` is a `TypeError`; only `CreateSigningUrlRequest::fromArray()` accepts the string value, and it throws `ValidationException` `InvalidPageNavigation` for anything else). It sets how the signing page shows its page thumbnails inside your iframe: `PageNavigation::AUTO` (the default) sizes it to the iframe width (the thumbnail rail is open at 1200px and wider, collapsed from 900px to 1199px, and a "Pages" button in the bottom bar under 900px), `PageNavigation::COLLAPSED` starts the rail collapsed at any width (the signer can still open it), and `PageNavigation::HIDDEN` shows no rail and no Pages button. It applies to that URL only: a later `createSigningUrl()` call without it resets to `AUTO`, so pass it on every mint. It has no effect when the URL is opened outside an iframe. - **`identityAssertion` is for external_idv only** — pass it here when the recipient's mode is `external_idv` (see below). Sending it for an OTP, override, or no-verification recipient is rejected with a 400 `IdentityModeConflict` error, not ignored. ### Identity verification modes @@ -602,6 +605,7 @@ use TurboDocx\Types\Requests\CreateEmbeddedSignatureRequest; use TurboDocx\Types\Requests\EmbeddedSignatureRecipient; use TurboDocx\Types\Requests\EmbeddedRecipientAuth; use TurboDocx\Types\Requests\EmbeddedRecipientFields; +// use TurboDocx\Types\PageNavigation; // only if you set pageNavigation below try { $result = TurboSign::createEmbeddedSignature(new CreateEmbeddedSignatureRequest( @@ -623,6 +627,7 @@ try { // your app shows the signing page. Passcode and completed-copy emails still go out. sendEmail: false, returnUrl: 'https://app.yourcompany.com/signed', + // pageNavigation: PageNavigation::HIDDEN, // optional, passed to each embed URL: AUTO (default) | COLLAPSED | HIDDEN )); } catch (AuthorizationException $e) { if ($e->errorCode === 'OtpOverrideNotAllowed') { @@ -644,6 +649,8 @@ foreach ($result->recipients as $r) { // one entry per signer, in signing orde `CreateEmbeddedSignatureResponse` carries `documentId` and a `recipients` array; each `EmbeddedSignatureRecipientResult` has `recipientId`, `name`, `email`, `status`, `identityVerificationMode`, and `embedUrl`. It is **turn-aware**: with a sequential signing order the backend only mints a URL for the signer whose turn it is, so a later signer comes back `status: 'pending'` with `embedUrl: null` (re-mint with `createSigningUrl()` once earlier signers finish), and an already-signed one comes back `completed`. Any genuine error still throws. For a `ready` signer `identityVerificationMode` is the mode the backend resolved for the URL; for `pending` / `completed` no URL was minted, so it is the mode you **requested** via `auth` (`null` when you set none, even if the org's default channel will apply). `createSigningUrl()` reports the effective mode. +`returnUrl` and `pageNavigation` are passed to each embed URL. `pageNavigation` is not saved on the document: when you re-mint a `pending` signer's URL with `createSigningUrl()`, pass the same `pageNavigation` again or that signer gets the default `AUTO` navigation. + The `auth` shorthand expresses **OTP or no verification only** (`emailOtp` / `smsPhoneNumber`). No `auth` means the org's `defaultChannel` applies; an `auth` channel that differs from a locked default fails the whole call with `OtpOverrideNotAllowed`. For `external_idv` or `override`, use the `sendSignature()` + `createSigningUrl()` path above with `IdentityVerification::externalIdv()` / `::override()` on the `Recipient`. A top-level `fields:` array on `CreateEmbeddedSignatureRequest` replaces the recipients' `fields` shorthand. An empty top-level `fields` counts as absent, so the shorthand still expands (0.8.0 sent the empty list and dropped it). diff --git a/skills/turbodocx-sdk/references/python.md b/skills/turbodocx-sdk/references/python.md index 59c887c..2231386 100644 --- a/skills/turbodocx-sdk/references/python.md +++ b/skills/turbodocx-sdk/references/python.md @@ -408,6 +408,7 @@ link = await TurboSign.create_signing_url( external_id="your_customer_123", # select the recipient by YOUR key... # recipient_id="rec-uuid", # ...OR by TurboDocx's recipient id — pass EXACTLY ONE return_url="https://app.yourcompany.com/signed", # optional; https only + # page_navigation="collapsed", # optional: page thumbnails in the iframe: "auto" (default) | "collapsed" | "hidden" ) print(link["url"]) # open / redirect / iframe this @@ -418,6 +419,8 @@ print(link["expiresAt"]) # see below; None when the document doe The response also carries `recipientId` and (when set) `externalId`. Select the recipient with **exactly one** of `external_id` / `recipient_id` — zero or both raises `ValidationError(code="RecipientSelectorInvalid")` before any HTTP call; a non-`https` `return_url` raises `InvalidReturnUrl`. +`page_navigation=` is optional; leave it out unless the user asks to change the page navigation. It sets how the signing page shows its page thumbnails inside your iframe (sent as `pageNavigation`; the type is `turbodocx_sdk.PageNavigationMode`): `"auto"` (the default) sizes it to the iframe width (the thumbnail rail is open at 1200px and wider, collapsed from 900px to 1199px, and a "Pages" button in the bottom bar under 900px), `"collapsed"` starts the rail collapsed at any width (the signer can still open it), and `"hidden"` shows no rail and no Pages button. It applies to that URL only: a later `create_signing_url` call without it resets to `"auto"`, so pass it on every mint. It has no effect when the URL is opened outside an iframe. + **The URL's lifetime depends on the recipient's mode:** - **No verification or `otp`**: the reusable signing link, which survives a refresh. `expiresAt` is the **document's own expiry**, or `None` when the document doesn't expire. @@ -490,6 +493,7 @@ try: }, ], # return_url="https://app.yourcompany.com/signed", # optional; passed to each embed URL + # page_navigation="hidden", # optional; passed to each embed URL: "auto" (default) | "collapsed" | "hidden" ) except AuthorizationError as e: if e.code == "OtpOverrideNotAllowed": @@ -506,7 +510,7 @@ Returns `documentId` plus `recipients`; each entry has `recipientId`, `name`, `e **`status` is turn-aware** — with a sequential signing order the backend only mints a URL for the signer whose turn it is: - `'ready'` — it's their turn; `embedUrl` is set, frame it now. -- `'pending'` — an earlier signer hasn't finished; `embedUrl` is `None`. Re-mint with `create_signing_url` once earlier signers complete. +- `'pending'` — an earlier signer hasn't finished; `embedUrl` is `None`. Re-mint with `create_signing_url` once earlier signers complete. `page_navigation` is not saved on the document, so pass the same value again when you re-mint, or that signer gets the default `"auto"` navigation. - `'completed'` — they already signed; `embedUrl` is `None`. This is one underlying condition seen from both methods: `create_signing_url` **throws** `RecipientNotInTurn` / `RecipientAlreadySigned`, while `create_embedded_signature` **catches those codes** and degrades them to `pending` / `completed`. (The SDK also catches `NotSignersTurn` for cross-SDK parity; it is reserved and the current API never returns it, so don't write handling for it.) Any other error propagates. An SMS-OTP recipient with no `phone` (E.164) raises `PhoneRequiredForSmsOtp` before the send. @@ -1520,7 +1524,7 @@ The exception attributes are `e.status_code` (int or `None`) and `e.code` (str) | `TurboSign.void_document(document_id, reason)` | Cancel a signature request (reason required) | | `TurboSign.resend_email(document_id, recipient_ids)` | Resend signature email to recipient UUIDs | | `TurboSign.get_audit_trail(document_id)` | Get complete audit trail | -| `TurboSign.create_signing_url(document_id, recipient_id=/external_id=, identity_assertion=, return_url=)` | Mint an embedded signing URL for one recipient (exactly one selector) | +| `TurboSign.create_signing_url(document_id, recipient_id=/external_id=, identity_assertion=, return_url=, page_navigation=)` | Mint an embedded signing URL for one recipient (exactly one selector) | | `TurboSign.get_embedded_signing_settings()` | Read the org's read-only embedded-signing gates (`enabled`, `allowExternalIdv`, `allowIdentityOverride`, `defaultChannel`, `allowChannelOverride`, `allowedFrameAncestors`) | | `TurboSign.create_embedded_signature(recipients, ...)` | One call: send + mint a per-recipient embed URL; recipients carry `status` (`ready`/`pending`/`completed`) + `embedUrl` | | `Deliverable.configure(api_key, org_id, base_url=...)` | Configure the deliverable client (no sender_email needed) | diff --git a/skills/turbodocx-sdk/references/ruby.md b/skills/turbodocx-sdk/references/ruby.md index 43a325e..83de04a 100644 --- a/skills/turbodocx-sdk/references/ruby.md +++ b/skills/turbodocx-sdk/references/ruby.md @@ -384,7 +384,7 @@ Embedded signing takes a signer from **your own app** straight to a TurboSign si Three methods cover the flow: `get_embedded_signing_settings` reads the org's gates, `create_signing_url` mints one recipient's URL **at click time**, and `create_embedded_signature` does the prepare-and-mint in a single call. -> **KEY GOTCHA — keyword args are snake_case, request-hash keys stay camelCase.** `recipient_id:`, `external_id:`, `identity_assertion:`, `return_url:` follow Ruby convention (like `api_key:`), but the keys **inside** any hash you pass — the `identity_assertion:` hash (`provider`, `verificationId`, `verifiedAt`, `subjectEmail`) and a recipient's `identityVerification` hash (`mode`, `channel`, `overrideIdentityVerification`) — are forwarded to the API verbatim and must stay **camelCase**, exactly as everywhere else in this SDK. +> **KEY GOTCHA — keyword args are snake_case, request-hash keys stay camelCase.** `recipient_id:`, `external_id:`, `identity_assertion:`, `return_url:`, `page_navigation:` follow Ruby convention (like `api_key:`), but the keys **inside** any hash you pass — the `identity_assertion:` hash (`provider`, `verificationId`, `verifiedAt`, `subjectEmail`) and a recipient's `identityVerification` hash (`mode`, `channel`, `overrideIdentityVerification`) — are forwarded to the API verbatim and must stay **camelCase**, exactly as everywhere else in this SDK. #### Identity modes @@ -449,6 +449,7 @@ link = TurboDocxSdk::TurboSign.create_signing_url( sent["documentId"], external_id: "your_customer_123", # XOR recipient_id: — exactly one return_url: "https://app.example.com/signed" # https only; where the signer returns after signing + # page_navigation: "collapsed" # optional: page thumbnails in the iframe: "auto" (default) | "collapsed" | "hidden" ) puts link["url"] # embeddable signing URL — open in a tab, redirect, or iframe @@ -484,6 +485,8 @@ The URL comes in **two flavors depending on the recipient's mode**, and this dri - **No verification or `otp`** → a **reusable** `?token=` link that survives a refresh; `expiresAt` is the **document's own expiry**, or `nil` when the document doesn't expire. For `otp` the page asks for the passcode first. - **`external_idv` or `override`** → a **single-use** `?sut=` link that is redeemed once when opened and expires 5 minutes after issue. Mint a fresh one each time; do **not** reuse or store it. +`page_navigation:` is optional; leave it out unless the user asks to change the page navigation. It sets how the signing page shows its page thumbnails inside your iframe (sent as `pageNavigation`): `"auto"` (the default) sizes it to the iframe width (the thumbnail rail is open at 1200px and wider, collapsed from 900px to 1199px, and a "Pages" button in the bottom bar under 900px), `"collapsed"` starts the rail collapsed at any width (the signer can still open it), and `"hidden"` shows no rail and no Pages button. It applies to that URL only: a later `create_signing_url` call without it resets to `"auto"`, so pass it on every mint. It has no effect when the URL is opened outside an iframe. + ### TurboSign.create_embedded_signature Prepare a document **and** mint a per-recipient embed URL in **one call** — the counterpart of a create-with-embed flow. It is a thin wrapper over `send_signature` + `create_signing_url`, so `sendEmail` defaults to **false** (the host app owns the signer UX). Recipients come back **in signing order**. @@ -505,6 +508,8 @@ result = TurboDocxSdk::TurboSign.create_embedded_signature( fields: { signature: "{signature2}" } } ] # fields: [...] # optional top-level full field objects; overrides the per-recipient shorthand + # returnUrl: "https://app.example.com/signed", # optional, https, passed to each embed URL + # pageNavigation: "hidden", # optional, passed to each embed URL: "auto" (default) | "collapsed" | "hidden" ) rescue TurboDocxSdk::AuthorizationError => e if e.code == "OtpOverrideNotAllowed" @@ -522,6 +527,7 @@ result["recipients"].each do |r| end ``` +- **`returnUrl:` and `pageNavigation:` are passed to each embed URL.** `pageNavigation` is not saved on the document: when you re-mint a `pending` signer with `create_signing_url`, pass the same `page_navigation:` again or that signer gets the default `"auto"` navigation. - **`identityVerificationMode` on a `pending` / `completed` signer** is the mode you **requested** via `auth:` (`nil` when you set none, even if the org's default channel will apply); for a `ready` signer it is what the backend resolved. `create_signing_url` reports the effective mode. - **No `auth:` means the org's `defaultChannel` applies.** An `auth:` channel that differs from a locked default fails the whole call with `OtpOverrideNotAllowed`. - **The `auth:` shorthand only produces OTP.** `auth: { emailOtp: true }` maps to `{ mode: "otp", channel: "email" }`; `auth: { sms: { phoneNumber: "..." } }` maps to `{ mode: "otp", channel: "sms" }` and sets the recipient's phone. `emailOtp` wins when both are set. `external_idv` and `override` are **not** reachable from this shorthand — use the two-call path (`send_signature` with an explicit `identityVerification` hash, then `create_signing_url`) for those. @@ -1381,7 +1387,7 @@ The error classes are **not** nested under a sub-module (e.g. not `TurboDocxSdk: | `TurboDocxSdk::TurboSign.get_template_signature_setup(template_id)` | List a template's signer roles (`key`, `order`, `hasSavedSigner`, `fieldCount`) to send it with recipients' `role` | | `TurboDocxSdk::TurboSign.get_status(document_id)` | Get document-level status + expiresAt (no recipients) | | `TurboDocxSdk::TurboSign.get_embedded_signing_settings` | Read the org's embedded-signing gates (enabled, allowExternalIdv, allowIdentityOverride, defaultChannel, allowChannelOverride, allowedFrameAncestors) | -| `TurboDocxSdk::TurboSign.create_signing_url(document_id, recipient_id:/external_id:, identity_assertion:, return_url:)` | Mint one recipient's embedded signing URL at click time (exactly one selector) | +| `TurboDocxSdk::TurboSign.create_signing_url(document_id, recipient_id:/external_id:, identity_assertion:, return_url:, page_navigation:)` | Mint one recipient's embedded signing URL at click time (exactly one selector) | | `TurboDocxSdk::TurboSign.create_embedded_signature(request)` | Prepare a document and mint a per-recipient embed URL in one call (per-recipient `status`/`embedUrl`) | | `TurboDocxSdk::TurboSign.download(document_id)` | Download signed PDF as raw bytes | | `TurboDocxSdk::TurboSign.void_document(document_id, reason)` | Cancel a signature request (reason required) |