Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions evals/evals.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
}
]
}
1 change: 1 addition & 0 deletions skills/turbodocx-sdk/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` (`<TurboSignForm>` / `<turbosign-form>`): 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 `<TurboSignForm>` 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.
Expand Down
4 changes: 4 additions & 0 deletions skills/turbodocx-sdk/references/go.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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.
Expand Down
Loading