OpenAI Function Calling + Signbee: Dispatch E-Sign from Custom Agents (2026)
To equip custom OpenAI agents with autonomous e-signature capabilities without relying on desktop Model Context Protocol (MCP) clients, declare a lightweight JSON Schema tool wrapping Signbee's REST endpoint and execute an HTTP POST to https://signb.ee/api/v1/send whenever the model emits a tool call. This enables headless server-side agents running in Node.js, Python, or edge runtimes to compose two-party markdown agreements on the fly, verify dispatches via instant email OTP or Bearer API keys, and deliver legally binding contracts sealed with cryptographic SHA-256 audit certificates.
Founder, Signbee (B2bee Ltd)
Integration Time
Required for OTP
Schema Tokens
Sealed Audit Trail
- Headless independence: While Claude Desktop and Cursor rely on local stdio MCP processes, custom backend agents (FastAPI, Next.js, LangGraph) use native OpenAI function calling to dispatch HTTP requests directly to Signbee without any desktop host.
- Minimalist tool declaration: Provide an OpenAI-compatible
send_documenttool schema definingrecipient_name,recipient_email, andmarkdown(min 10 characters). It adds just ~260 tokens to your system context. - Dual authentication modes: Test without an API key using zero-friction sender email OTP (returns
pending_sender), or supply anAuthorization: Bearer <api_key>header for fully autonomous, instant dispatches (returnspending_recipient). - Chat Completions vs Responses API: Implement standard
toolsin Chat Completions for existing agent stacks, or adopt the newer OpenAI Responses API for native conversational state loops and unified execution surfaces. - Real-time lifecycle tracking: Pro and Business users can pass a
webhook_urlto receive an HMAC SHA-256 signeddocument.signednotification upon completion, or query status viaGET /api/v1/documents/{id}. - Legal enforceability: Every agreement complies with the US ESIGN Act, EU eIDAS (SES), and UK ECA 2000, culminating in an immutable PDF sealed with an audit certificate and SHA-256 checksum.
Watch — OpenAI Function Calling + Signbee — https://www.youtube.com/watch?v=BC4LJCUJbTo
1. Why OpenAI Function Calling vs Desktop MCP Hosts
In our earlier tutorial on Claude Desktop Signbee MCP Setup, we walked through installing the signbee-mcp server into Anthropic's desktop application. That setup works brilliantly for interactive desktop users who want Claude to draft agreements and trigger signing ceremonies from an interactive chat window. However, modern enterprise software architectures rarely live inside desktop GUI windows.
Autonomous enterprise agents operate in headless cloud environments: Docker containers on AWS ECS, serverless handlers on Vercel or Cloudflare Workers, Python background workers running Celery or Temporal, and agentic graphs orchestrated by LangGraph, CrewAI, or AutoGen. In these headless production runtimes, there is no desktop GUI, no interactive user to approve dialog prompts, and no persistent terminal stdin/stdout pipe suitable for hosting local MCP stdio processes.
If your system runs on OpenAI's models—whether GPT-4o, GPT-4o-mini, or specialized fine-tuned models—you do not need an MCP bridge at all. As discussed in our analysis of Agent Document Signing: Skills vs MCP vs API, the cleanest and most robust integration pattern for backend agents is native function calling over standard HTTP.
By declaring a declarative JSON Schema tool within your OpenAI client configuration and executing an HTTP POST to Signbee's REST endpoint when the model invokes the tool, you achieve complete autonomy:
- Zero external daemons: You avoid running child processes, Node.js stdio wrappers, or custom MCP-to-REST adapters.
- Platform portability: The exact same tool definition runs identically in Python (FastAPI, Django), TypeScript (Node.js, Deno, Bun), Go, or Rust.
- Micro-footprint token overhead: While broad MCP server bundles often consume 1,500+ tokens of context just defining irrelevant system tools, a targeted Signbee tool schema consumes only ~260 tokens.
- Deterministic error propagation: HTTP status codes (such as 400 for validation errors or 403 for rate limits) return directly to your application runtime, allowing immediate programmatic retry or fallback handling.
2. Defining the OpenAI Tool Schema for Signbee
To give an OpenAI model the ability to send contracts, you provide a function tool definition in your request payload. The model inspects this schema, decides when contract execution is required based on the conversation context, and outputs a structured JSON argument payload matching your specifications.
It is critical to ground your schema in Signbee's production contract documented at signb.ee/openapi.json and signb.ee/llms.txt. Unlike high-level marketing descriptions that occasionally use abstract shorthand like document or parties, the production endpoint (POST https://signb.ee/api/v1/send) enforces specific, strongly-typed fields:
recipient_name(string, required): Full legal name of the signer.recipient_email(string, required): Valid email address of the signer.markdown(string, minimum 10 characters): The agreement terms written in standard GitHub Flavored Markdown. Eithermarkdownorpdf_urlis mandatory.pdf_url(string, optional): A publicly accessible URL to an existing PDF file if you are not generating the document from markdown.title(string, optional): Human-readable document title. If omitted, Signbee automatically extracts the first H1 header from the markdown.sender_nameandsender_email(strings, optional with API key, required without API key): Used to identify the originating sender and route the verification OTP when no API key is provided.expires_in_days(integer, optional): Duration until signing token expiration (defaults to 7 days).webhook_url(string, optional): Destination URL for asynchronousdocument.signedcallbacks (gated to Pro and Business accounts).
Here is the production-ready JSON Schema definition for OpenAI function calling, named send_document:
{
"type": "function",
"function": {
"name": "send_document",
"description": "Send a legally binding contract or agreement for digital signature via Signbee. Generates a signed PDF with cryptographic SHA-256 audit certification.",
"parameters": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "The title of the agreement (e.g., 'Mutual Non-Disclosure Agreement' or 'Master Services Agreement')."
},
"recipient_name": {
"type": "string",
"description": "Full legal name of the person or entity receiving the document to sign."
},
"recipient_email": {
"type": "string",
"description": "Email address where the secure signing link will be delivered."
},
"markdown": {
"type": "string",
"description": "The complete contract body formatted in standard CommonMark/GFM markdown. Must contain at least 10 characters."
},
"sender_name": {
"type": "string",
"description": "Full name of the sender. Required if no API key is configured in the environment."
},
"sender_email": {
"type": "string",
"description": "Email address of the sender. Required if no API key is configured in the environment."
},
"expires_in_days": {
"type": "integer",
"description": "Number of days before the signing link expires. Default is 7.",
"default": 7
},
"webhook_url": {
"type": "string",
"description": "Optional HTTPS URL to receive an HMAC-signed document.signed callback when completed (Pro/Business plans only)."
}
},
"required": ["recipient_name", "recipient_email", "markdown"]
}
}
}For scenarios where your agent handles pre-compiled PDF files (such as generated architectural blueprints, tax returns, or scanned lease agreements), you can optionally declare a secondary tool called send_document_pdf. The parameter structure is identical, replacing the markdown property with pdf_url:
{
"type": "function",
"function": {
"name": "send_document_pdf",
"description": "Send a pre-existing hosted PDF document for digital signature via Signbee.",
"parameters": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "The title of the PDF document."
},
"recipient_name": {
"type": "string",
"description": "Full legal name of the signer."
},
"recipient_email": {
"type": "string",
"description": "Email address of the signer."
},
"pdf_url": {
"type": "string",
"description": "Direct, publicly accessible HTTPS URL to the PDF file."
},
"sender_name": { "type": "string" },
"sender_email": { "type": "string" },
"expires_in_days": { "type": "integer", "default": 7 }
},
"required": ["recipient_name", "recipient_email", "pdf_url"]
}
}
}3. Chat Completions Tool-Call Loop (TypeScript Implementation)
Let us assemble a complete, runnable TypeScript implementation using the official openai npm package. In this architecture, our agent prompts the model to generate a custom consulting agreement. When the model invokes our send_document function, our application executes the HTTP request against Signbee's REST API, submits the tool execution result back to the model, and allows the model to summarize the final dispatch for the user.
This pattern is directly related to our guide on One API Call: Markdown to Signed PDF for AI Agents, showing how programmatic tool calling bridges language models and cryptographic signature pipelines.
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
const SIGNBEE_API_KEY = process.env.SIGNBEE_API_KEY; // Optional for OTP, recommended for headless
const SIGNBEE_ENDPOINT = "https://signb.ee/api/v1/send";
// 1. Declare the Signbee tool definition
const tools: OpenAI.ChatCompletionTool[] = [
{
type: "function",
function: {
name: "send_document",
description: "Send a legally binding contract for digital signature via Signbee.",
parameters: {
type: "object",
properties: {
title: { type: "string", description: "Title of the contract" },
recipient_name: { type: "string", description: "Legal name of the recipient" },
recipient_email: { type: "string", description: "Email of the recipient" },
markdown: { type: "string", description: "Markdown text of the agreement (min 10 chars)" },
sender_name: { type: "string", description: "Name of the sender" },
sender_email: { type: "string", description: "Email of the sender" },
expires_in_days: { type: "integer", default: 7 },
},
required: ["recipient_name", "recipient_email", "markdown"],
},
},
},
];
// 2. Concrete HTTP dispatcher executing the Signbee REST request
async function executeSignbeeSend(args: Record<string, any>) {
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (SIGNBEE_API_KEY) {
headers["Authorization"] = `Bearer ${SIGNBEE_API_KEY}`;
}
const payload = {
title: args.title,
recipient_name: args.recipient_name,
recipient_email: args.recipient_email,
markdown: args.markdown,
sender_name: args.sender_name || "Alice Chen",
sender_email: args.sender_email || "alice@startup.com",
expires_in_days: args.expires_in_days || 7,
};
const res = await fetch(SIGNBEE_ENDPOINT, {
method: "POST",
headers,
body: JSON.stringify(payload),
});
const data = await res.json();
if (!res.ok) {
throw new Error(`Signbee API returned ${res.status}: ${JSON.stringify(data)}`);
}
return data;
}
// 3. Autonomous agent invocation loop
async function runContractAgent() {
const messages: OpenAI.ChatCompletionMessageParam[] = [
{
role: "system",
content:
"You are an autonomous legal operations assistant. When instructed to dispatch an agreement, draft concise, professional legal terms in markdown and invoke the send_document tool.",
},
{
role: "user",
content:
"Draft a Cloud Consulting Agreement between Alice Chen (alice@startup.com) and Bob Smith (bob@acme.com) for a 3-month security audit at $150/hr, and send it for digital signature.",
},
];
// Initial turn: Model inspects prompt and outputs tool call
const completion = await openai.chat.completions.create({
model: "gpt-4o",
messages,
tools,
tool_choice: "auto",
});
const responseMessage = completion.choices[0].message;
messages.push(responseMessage);
if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {
for (const toolCall of responseMessage.tool_calls) {
if (toolCall.function.name === "send_document") {
console.log("Model requested document dispatch. Parsing arguments...");
const parsedArgs = JSON.parse(toolCall.function.arguments);
try {
const result = await executeSignbeeSend(parsedArgs);
console.log("Signbee dispatch succeeded:", result);
// Submit tool execution result back to the model
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify(result),
});
} catch (err: any) {
console.error("Signbee dispatch failed:", err.message);
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify({ error: err.message }),
});
}
}
}
// Final turn: Model reads tool result and provides conversational confirmation
const finalCompletion = await openai.chat.completions.create({
model: "gpt-4o",
messages,
});
console.log("\nAgent Final Response:\n", finalCompletion.choices[0].message.content);
} else {
console.log("No tool calls were generated by the model.");
}
}
runContractAgent().catch(console.error);When executed, this script performs a streamlined two-turn handshake:
- The model parses your natural language instruction, synthesizes comprehensive contractual terms (including confidentiality, scope of services, payment terms, and governing law), and outputs a structured tool invocation with
recipient_name: "Bob Smith"andrecipient_email: "bob@acme.com". - Your runtime catches the
send_documenttool call and dispatches the payload tohttps://signb.ee/api/v1/send. - Signbee returns a JSON envelope containing the unique
document_idand initial status. - The model processes the response and outputs a polished confirmation message to the user, providing the document ID, expiration timeline, and delivery status.
4. Responses API Variant: Modern Agentic Loops
In addition to the classic Chat Completions endpoint, modern OpenAI application architectures frequently build upon the newer OpenAI Responses API (openai.responses.create). The Responses API is designed from the ground up for agentic execution loops, featuring unified response objects, built-in conversation state continuity, and streamlined tool handling.
When should you choose the Responses API over Chat Completions?
- Choose Chat Completions: If you are working within established orchestration frameworks (such as LangChain, LlamaIndex, or AutoGen), maintaining legacy multi-turn arrays, or utilizing strict third-party proxy gateways that only normalize
/v1/chat/completions. - Choose Responses API: If you are building greenfield autonomous agent runtimes, implementing multimodal streaming pipelines, or creating stateful persistent agents that leverage server-managed conversation threads.
In the Responses API, the tool schema follows the exact same underlying JSON Schema specification, declared inside the tools parameter. Here is the concise implementation demonstrating how to handle tool outputs using the Responses endpoint:
import OpenAI from "openai";
const openai = new OpenAI();
async function runResponsesAgent() {
// Define tools array matching the Responses API specification
const response = await openai.responses.create({
model: "gpt-4o",
input: [
{
role: "user",
content: "Please draft an NDA for contractor David Vance (david@example.com) from sender legal@acme.com and send it via Signbee.",
},
],
tools: [
{
type: "function",
name: "send_document",
description: "Send a legally binding contract for digital signature via Signbee.",
parameters: {
type: "object",
properties: {
title: { type: "string" },
recipient_name: { type: "string" },
recipient_email: { type: "string" },
markdown: { type: "string" },
sender_name: { type: "string" },
sender_email: { type: "string" },
},
required: ["recipient_name", "recipient_email", "markdown"],
},
},
],
});
// Check for tool call output in the unified response structure
for (const item of response.output) {
if (item.type === "message" && item.content) {
console.log("Model Output:", item.content);
}
}
}5. Authentication Architecture: OTP vs Bearer API Key
Signbee was engineered around a core principle: zero friction is the product. When building an autonomous agent, you should not be blocked by mandatory account creation, credit card capture, or OAuth credential dances before you can dispatch a single document.
To support both developer experimentation and unattended production automation, Signbee provides two distinct authentication paths on the exact same endpoint:
| Feature | Zero-Config OTP Flow | API Key Flow (Bearer Token) |
|---|---|---|
| Header Required | None (omitted) | Authorization: Bearer <api_key> |
| Sender Fields | sender_name & sender_email mandatory | Optional (inferred from account profile) |
| Initial Status | pending_sender | pending_recipient |
| Sender Verification | 6-digit OTP code emailed to sender | Pre-verified cryptographic token |
| Recipient Delivery | Delivered after sender verifies OTP | Delivered immediately upon API response |
| Ideal Runtime | Interactive CLI, developer testing, manual sign-off | Autonomous background workers, microservices, SaaS |
The OTP Verification Flow: If your OpenAI agent dispatches a document without an API key, Signbee generates a high-entropy 6-digit numeric OTP and delivers it to the address specified in sender_email. The HTTP response returns:
{
"document_id": "cmm189xyz000108l41234abcd",
"status": "pending_sender",
"message": "Verification email sent to alice@startup.com. Complete setup to send the document."
}The recipient will not receive their signing email until the sender inputs this OTP. This safeguards against spam and prevents rogue scripts from impersonating senders.
The Authenticated Flow: When your production agent supplies an API key obtained from signb.ee/dashboard, Signbee verifies the account instantly. The sender signature is pre-authenticated, and the signing invitation is dispatched immediately to the recipient. The HTTP response returns:
{
"document_id": "cmm189xyz000108l41234abcd",
"status": "pending_recipient",
"sender": "Alice Chen",
"recipient": "Bob Smith",
"expires_at": "2026-09-15T16:00:00.000Z"
}6. Executing the Tool: Direct HTTP POST /api/v1/send
Whether your agent runtime is written in Python, Node.js, Go, or Ruby, executing the tool simply requires a standard HTTP client. Here is what the raw HTTP exchange looks like over the wire:
curl -X POST https://signb.ee/api/v1/send \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sb_live_9f8e7d6c5b4a3" \
-d '{
"title": "Mutual Non-Disclosure Agreement",
"recipient_name": "Bob Smith",
"recipient_email": "bob@acme.com",
"markdown": "# Mutual Non-Disclosure Agreement\n\nThis Agreement is entered into between Alice Chen and Bob Smith.\n\n## 1. Confidential Information\nBoth parties agree to hold all proprietary materials in strict confidence for a period of two (2) years.\n\n## 2. Governing Law\nThis Agreement shall be construed under the laws of the State of Delaware.",
"expires_in_days": 7
}'Notice how clean this payload is: no base64-encoded PDF binary uploads, no pre-rendered visual coordinate bounding boxes, and no complex XML envelopes. Signbee parses the markdown, applies elegant typography (including full Unicode and CJK font support via fontkit, as documented in our Japanese & CJK Document Signing Guide), generates a standardized PDF, and sends the counter-party their responsive signing link.
7. Lifecycle Management: Tracking Signatures (Webhooks vs Polling)
Sending the document is only half the battle. How does an autonomous OpenAI agent know when the human party has actually signed the agreement?
Because contract signing is fundamentally asynchronous—human signers typically take anywhere from a few minutes to several business days to review and execute legal terms—your agent should not hold an open synchronous connection waiting for completion. Instead, Signbee provides two distinct mechanisms for tracking document lifecycle events:
Option A: Webhook Callbacks (Pro & Business Plans)
As detailed in our dedicated guide on Agent Document Signing Webhooks, Pro and Business plan users can provide a webhook_url directly in the dispatch payload. When all signers complete the web-based signing ceremony, Signbee delivers an HTTP POST containing the document.signed event to your designated webhook listener:
{
"event": "document.signed",
"document_id": "cmm189xyz000108l41234abcd",
"title": "Mutual Non-Disclosure Agreement",
"signed_at": "2026-09-08T16:22:45.000Z",
"pdf_url": "https://signb.ee/documents/executed-cmm189xyz000108l41234abcd.pdf",
"certificate": {
"sha256": "8f4b23a1c890def7132a4e9b8c0d12e345f67a89b0c1d2e3f4a5b6c7d8e9f012",
"audit_trail_url": "https://signb.ee/certificate/cmm189xyz000108l41234abcd"
}
}Every webhook request is signed using HMAC SHA-256 via the X-Signbee-Signature header. When your agent registers a webhook, Signbee provides a unique webhook_secret in the initial response so your endpoint can verify payload authenticity before executing downstream business logic (such as unlocking SaaS seats or releasing escrow funds).
Option B: Programmatic Polling (All Plans with API Key)
If your agent runs as an ephemeral cron job or lacks a public inbound HTTPS listener, it can check document status on-demand using the document status endpoint:
curl -X GET https://signb.ee/api/v1/documents/cmm189xyz000108l41234abcd \ -H "Authorization: Bearer sb_live_9f8e7d6c5b4a3"
This endpoint returns the complete current document state, including signer timestamps, original PDF URL, signed PDF URL (once complete), and expiration timestamps. If an agreement becomes obsolete before being signed, your agent can revoke it immediately by issuing a DELETE /api/v1/documents/{id} request.
8. Common Pitfalls & Production Hardening
When moving OpenAI function calling into production, keep these common traps in mind:
- Inventing MCP in OpenAI runtimes: Developers familiar with Claude Desktop often attempt to spawn
npx -y signbee-mcpinside Docker containers or serverless functions. This adds massive overhead and fragility. For OpenAI agents, execute HTTP POST requests directly tohttps://signb.ee/api/v1/send. - Missing sender fields in unauthenticated mode: When running without an API key, omitting
sender_nameorsender_emailtriggers an immediate400 Bad Requesterror. Ensure both sender and recipient fields are populated if your agent tests unauthenticated dispatches. - Passing webhook_url on the Free tier: Webhook dispatch is an enterprise capability reserved for Pro and Business plans. If an agent passes a
webhook_urlwith a Free tier API key or unauthenticated request, Signbee returns a403 Forbiddenstatus code explicitly informing you that webhooks require an upgrade. - Markdown length constraint: The
markdownparameter requires at least 10 non-whitespace characters. Passing an empty string or a placeholder shorter than 10 characters returns a400error. - Overclaiming legal certification: Signbee generates legally binding Simple Electronic Signatures (SES) complying with the US ESIGN Act, EU eIDAS Regulation, and UK ECA 2000. It does not issue Qualified Electronic Signatures (QES) requiring national hardware smartcards. Accurately represent your compliance posture in your agent prompts.
Frequently Asked Questions
Why use OpenAI function calling with Signbee instead of Claude Desktop or Cursor MCP?
Claude Desktop and Cursor run the Model Context Protocol (MCP) over local stdio child processes, which requires a graphical desktop operating system and an interactive user interface. Custom autonomous agents running in headless production environments—such as Next.js API routes, AWS Lambda, Cloudflare Workers, FastAPI services, or LangGraph orchestration clusters—do not have desktop MCP hosts. OpenAI function calling and the Responses API enable these headless backend agents to declare Signbee tools directly and dispatch contracts over standard HTTP without any desktop GUI dependencies.
What parameters are required to send a document through the Signbee API?
Every dispatch to POST https://signb.ee/api/v1/send requires recipient_name, recipient_email, and either markdown (minimum 10 characters) or pdf_url. If calling without an Authorization: Bearer <api_key> header, sender_name and sender_email are also required to deliver a verification OTP. Optional parameters include title, expires_in_days (defaulting to 7), and webhook_url (available on Pro and Business tiers).
How does an OpenAI agent authenticate with Signbee?
Signbee supports two modes. For zero-friction testing and human-in-the-loop workflows, agents can omit the API key; Signbee dispatches a 6-digit email OTP to the sender, returning a pending_sender status until confirmed. For autonomous headless execution in production, passing an Authorization: Bearer <api_key> header bypasses OTP verification entirely, dispatching the signing email to the counter-party immediately with a pending_recipient status.
When should I use the OpenAI Responses API versus Chat Completions for Signbee?
Use Chat Completions (openai.chat.completions.create with tools) when integrating into established LangChain, LlamaIndex, or legacy multi-turn conversational pipelines that already implement standard tool-call message loops. Use the newer Responses API (openai.responses.create) when architecting next-generation autonomous agent loops that benefit from native state management, multimodal streaming, and unified tool execution surfaces.
How does an OpenAI agent know when the document has been signed?
Agents on Pro or Business tiers can provide a webhook_url when invoking the tool. Signbee delivers an asynchronous, HMAC-signed document.signed POST event when signing is completed. Alternatively, agents can query document lifecycle status on-demand via GET https://signb.ee/api/v1/documents/{id} using their API key to verify signatures and retrieve the final signed PDF URL.
Are contracts dispatched via OpenAI function calling legally enforceable?
Yes. Signbee contracts satisfy the United States ESIGN Act (15 U.S.C. § 7001), EU eIDAS Regulation (Simple Electronic Signatures), and the UK Electronic Communications Act 2000. When all parties complete the signing ceremony, Signbee stamps the executed PDF with a tamper-evident audit certificate embedding a cryptographic SHA-256 digest, UTC timestamps, IP addresses, and email verification markers.
Related resources
Build Signing into Your Autonomous Agent in Minutes
Call the Signbee REST API with zero friction using email OTP, or grab an API key for instant headless dispatches.