Guides

Make GitHub Copilot Write Correct Firma.dev Integration Code

GitHub Copilot is good at writing code that looks right. Integration code is where that becomes a problem. Ask it to wire up an e-signature API and it will confidently invent an endpoint, guess a payload shape, or reach for a Bearer prefix that your provider does not use. You end up debugging the assistant instead of shipping the feature.

The fix is to give Copilot the real API contract while it works. Firma.dev ships a Docs MCP server and a set of repo conventions that, together, get Copilot generating correct integration code on the first pass. Since Firma.dev is an API-first e-signature platform billed at €0.049 per envelope (~5¢ USD) with no seats or minimums, the integration itself is small. The point is to let Copilot write that small integration correctly instead of plausibly.

This covers three setup steps: connect the Docs MCP, add a repo-wide instructions file, and give Copilot the reference patterns to match.

Step 1: connect the Firma.dev Docs MCP

The Docs MCP server exposes Firma.dev's full documentation as searchable tools. With it connected, Copilot queries real endpoints, current request shapes, and webhook payload schemas instead of guessing. It is read-only and unauthenticated, so you never pass it an API key.

In VS Code, add it at the workspace level in .vscode/mcp.json:

{
  "servers": {
    "firma-docs": {
      "type": "http",
      "url": "https://docs.firma.dev/mcp"
    }
  }
}
{
  "servers": {
    "firma-docs": {
      "type": "http",
      "url": "https://docs.firma.dev/mcp"
    }
  }
}
{
  "servers": {
    "firma-docs": {
      "type": "http",
      "url": "https://docs.firma.dev/mcp"
    }
  }
}

Reload the window and confirm firma-docs shows up in the Copilot Chat tools picker. For the Copilot CLI, add the same server to ~/.copilot/mcp-config.json and restart. In JetBrains or Visual Studio, open Settings → Copilot → MCP Servers and add an HTTP server named firma-docs with the URL https://docs.firma.dev/mcp.

Step 2: add a repo-wide Copilot instructions file

.github/copilot-instructions.md is loaded automatically by Copilot Chat, the coding agent, and code review across every supported environment. This is where you pin the conventions that keep generated code on contract. Create or append to the file:

## Firma.dev e-signature integration

When generating code that sends, receives, or embeds Firma e-signatures, follow these conventions:

- API base URL: `https://api.firma.dev/functions/v1/signing-request-api`
- Auth: header `Authorization` set to the raw Firma API key (no `Bearer` prefix)
- The key lives in `FIRMA_API_KEY` as an env var. Call Firma from the backend only, never the browser. Never commit the value.
- Default to `POST /signing-requests/create-and-send` when sending from a template. Only use the two-step `POST /signing-requests` then `POST /signing-requests/{id}/send` flow when the user needs to review the draft before sending.
- Recipient object shape: `{ first_name, last_name, email, designation: "Signer", order: 1 }`
- For webhook handlers, branch on `payload.type` (e.g. `signing_request.completed`). The signing request object lives at `payload.data.signing_request`.
- For embedded signing, iframe `https://app.firma.dev/signing/{signing_request_user_id}` with `allow="camera;microphone;clipboard-write"`.
- When unsure about an endpoint or field shape, use the `firma-docs` MCP server. Do not guess

## Firma.dev e-signature integration

When generating code that sends, receives, or embeds Firma e-signatures, follow these conventions:

- API base URL: `https://api.firma.dev/functions/v1/signing-request-api`
- Auth: header `Authorization` set to the raw Firma API key (no `Bearer` prefix)
- The key lives in `FIRMA_API_KEY` as an env var. Call Firma from the backend only, never the browser. Never commit the value.
- Default to `POST /signing-requests/create-and-send` when sending from a template. Only use the two-step `POST /signing-requests` then `POST /signing-requests/{id}/send` flow when the user needs to review the draft before sending.
- Recipient object shape: `{ first_name, last_name, email, designation: "Signer", order: 1 }`
- For webhook handlers, branch on `payload.type` (e.g. `signing_request.completed`). The signing request object lives at `payload.data.signing_request`.
- For embedded signing, iframe `https://app.firma.dev/signing/{signing_request_user_id}` with `allow="camera;microphone;clipboard-write"`.
- When unsure about an endpoint or field shape, use the `firma-docs` MCP server. Do not guess

## Firma.dev e-signature integration

When generating code that sends, receives, or embeds Firma e-signatures, follow these conventions:

- API base URL: `https://api.firma.dev/functions/v1/signing-request-api`
- Auth: header `Authorization` set to the raw Firma API key (no `Bearer` prefix)
- The key lives in `FIRMA_API_KEY` as an env var. Call Firma from the backend only, never the browser. Never commit the value.
- Default to `POST /signing-requests/create-and-send` when sending from a template. Only use the two-step `POST /signing-requests` then `POST /signing-requests/{id}/send` flow when the user needs to review the draft before sending.
- Recipient object shape: `{ first_name, last_name, email, designation: "Signer", order: 1 }`
- For webhook handlers, branch on `payload.type` (e.g. `signing_request.completed`). The signing request object lives at `payload.data.signing_request`.
- For embedded signing, iframe `https://app.firma.dev/signing/{signing_request_user_id}` with `allow="camera;microphone;clipboard-write"`.
- When unsure about an endpoint or field shape, use the `firma-docs` MCP server. Do not guess

Commit it. From then on, a prompt like "add a Firma.dev signing request when the user clicks Send Contract" starts with the right defaults baked in. If your repo has a clear backend boundary, you can add a path-specific file at .github/instructions/firma.instructions.md with an applyTo glob so the rules load only when Copilot edits your server code, which keeps the global file short.

Step 3: give Copilot the reference patterns

Copilot writes in whatever language matches the surrounding files. The shapes below are the canonical Firma.dev calls. The instructions file steers Copilot toward them and the MCP fills in the current details.

Create and send in TypeScript:

const FIRMA_API = "https://api.firma.dev/functions/v1/signing-request-api";

export async function sendForSignature(opts: {
  templateId: string;
  signer: { firstName: string; lastName: string; email: string };
}) {
  const res = await fetch(`${FIRMA_API}/signing-requests/create-and-send`, {
    method: "POST",
    headers: {
      Authorization: process.env.FIRMA_API_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      template_id: opts.templateId,
      recipients: [
        {
          first_name: opts.signer.firstName,
          last_name: opts.signer.lastName,
          email: opts.signer.email,
          designation: "Signer",
          order: 1,
        },
      ],
    }),
  });

  if (!res.ok) throw new Error(`Firma error: ${res.status}`);
  return res.json();
}
const FIRMA_API = "https://api.firma.dev/functions/v1/signing-request-api";

export async function sendForSignature(opts: {
  templateId: string;
  signer: { firstName: string; lastName: string; email: string };
}) {
  const res = await fetch(`${FIRMA_API}/signing-requests/create-and-send`, {
    method: "POST",
    headers: {
      Authorization: process.env.FIRMA_API_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      template_id: opts.templateId,
      recipients: [
        {
          first_name: opts.signer.firstName,
          last_name: opts.signer.lastName,
          email: opts.signer.email,
          designation: "Signer",
          order: 1,
        },
      ],
    }),
  });

  if (!res.ok) throw new Error(`Firma error: ${res.status}`);
  return res.json();
}
const FIRMA_API = "https://api.firma.dev/functions/v1/signing-request-api";

export async function sendForSignature(opts: {
  templateId: string;
  signer: { firstName: string; lastName: string; email: string };
}) {
  const res = await fetch(`${FIRMA_API}/signing-requests/create-and-send`, {
    method: "POST",
    headers: {
      Authorization: process.env.FIRMA_API_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      template_id: opts.templateId,
      recipients: [
        {
          first_name: opts.signer.firstName,
          last_name: opts.signer.lastName,
          email: opts.signer.email,
          designation: "Signer",
          order: 1,
        },
      ],
    }),
  });

  if (!res.ok) throw new Error(`Firma error: ${res.status}`);
  return res.json();
}

A webhook handler branches on the event type and reads the signing request from data:

export async function POST(req: Request) {
  const payload = await req.json();
  const { type, data } = payload;

  if (type === "signing_request.completed") {
    const signingRequestId = data.signing_request.id;
    // Update the matching record and trigger follow-up logic
  }

  return new Response(JSON.stringify({ received: true }), { status: 200 });
}
export async function POST(req: Request) {
  const payload = await req.json();
  const { type, data } = payload;

  if (type === "signing_request.completed") {
    const signingRequestId = data.signing_request.id;
    // Update the matching record and trigger follow-up logic
  }

  return new Response(JSON.stringify({ received: true }), { status: 200 });
}
export async function POST(req: Request) {
  const payload = await req.json();
  const { type, data } = payload;

  if (type === "signing_request.completed") {
    const signingRequestId = data.signing_request.id;
    // Update the matching record and trigger follow-up logic
  }

  return new Response(JSON.stringify({ received: true }), { status: 200 });
}

With these patterns in the repo, Copilot has something concrete to match rather than a blank page to improvise against.

Bonus: the Copilot coding agent

If your repo is set up for the Copilot coding agent, the same .github/copilot-instructions.md applies. Once the MCP and instructions are in place, an issue like "Add a Send Contract button that sends a Firma.dev signing request from template tmpl_abc123" turns into a PR that wires the env var, the backend handler, the webhook route, and the UI button without much hand-holding. Ask it to surface the webhook URL and template ID as TODOs in the PR description so your reviewer can register the webhook before merging. Even with automation this good, you still want a human to recieve and check that PR before it ships.

📘 Read the full GitHub Copilot integration guide
Every config file, path-specific rule, and reference pattern is in the docs: https://docs.firma.dev/guides/github-copilot-integration

Get started

Connect the MCP, drop in the instructions file, and Copilot stops guessing at your integration. Get started with Firma.dev for free, no credit card required, and let your assistant write the e-signature code correctly the first time.

  1. Titre

Image de fond

Prêt à ajouter des signatures électroniques à votre application ?

Commencez gratuitement. Aucune carte de crédit requise. Payez seulement 0,029 € par enveloppe lorsque vous êtes prêt à passer en direct.

Image de fond

Prêt à ajouter des signatures électroniques à votre application ?

Commencez gratuitement. Aucune carte de crédit requise. Payez seulement 0,029 € par enveloppe lorsque vous êtes prêt à passer en direct.

Image de fond

Prêt à ajouter des signatures électroniques à votre application ?

Commencez gratuitement. Aucune carte de crédit requise. Payez seulement 0,029 € par enveloppe lorsque vous êtes prêt à passer en direct.