@anvilco/anvil

中文文档
4.2.0发布于 2026/11/03MITTypeScriptESM + CJS构建来源已验证近 30 天下载 27万

Anvil API Client

原始使用文档
文档目录12 节
  1. Usage
  2. TypeScript
  3. API
  4. Instance Methods
  5. Class Methods
  6. Options
  7. Constants
  8. Errors
  9. Rate Limits
  10. API Documentation
  11. Examples
  12. Development

Horizontal Lockupblack Horizontal Lockup copywhite

Anvil API Client for Node

This library will allow you to interact with the Anvil API in JavaScript / NodeJS.

Anvil provides easy APIs for all things paperwork.

  1. PDF filling API - fill out a PDF template with a web request and structured JSON data.
  2. PDF generation API - send markdown or HTML and Anvil will render it to a PDF.
  3. Etch e-sign with API - customizable, embeddable, e-signature platform with an API to control the signing process end-to-end.
  4. Anvil Workflows (w/ API) - Webforms + PDF + e-sign with a powerful no-code builder. Easily collect structured data, generate PDFs, and request signatures.

Learn more on our Anvil developer page. See the API guide and the GraphQL reference for full documentation.

Usage

This library and the Anvil APIs are intended to be used on a server or server-like environment. It will fail in a browser environment.

Requires Node 20 or later.

yarn add @anvilco/anvil

It works as a CommonJS module or an ES module, in JavaScript or TypeScript:

const Anvil = require('@anvilco/anvil')
// or
import Anvil from '@anvilco/anvil'

TypeScript

The client's types come from @anvilco/types, which is generated from the Anvil API's GraphQL schema and OpenAPI spec and installed with the client. The variables of each GraphQL method and the payloads of fillPDF and generatePDF are typed from them, so a misspelled or missing argument is a compile error. For your own queries, and for the types of what the API returns, use its GraphQL namespace:

import type { GraphQL } from '@anvilco/types'

type Packet = GraphQL.Type<'EtchPacket'>
type PacketArgs = GraphQL.MutationArgs<'createEtchPacket'>

The arguments of createEtchPacket that the GraphQL schema can only call JSON are typed too, and exported by name for building them up separately:

import type { EtchSigner } from '@anvilco/anvil'

const signer: EtchSigner = {
  id: 'signer1',
  name: 'Sally Signer',
  email: '[email protected]',
  signerType: 'embedded',
  fields: [{ fileId: 'lease', fieldId: 'signature1' }],
}

If you pass your own mutation to a method, its variables may differ from the API's arguments for that mutation; cast them if TypeScript objects.

npm install @anvilco/anvil

A basic example converting your JSON to a filled PDF, then saving the PDF to a file:

import fs from 'fs'
import Anvil from '@anvilco/anvil'

// The ID of the PDF template to fill
const pdfTemplateID = 'kA6Da9CuGqUtc6QiBDRR'
// Your API key from your Anvil organization settings
const apiKey = '7j2JuUWmN4fGjBxsCltWaybHOEy3UEtt'

// JSON data to fill the PDF
const exampleData = {
  "title": "My PDF Title",
  "fontSize": 10,
  "textColor": "#CC0000",
  "data": {
    "someFieldId": "Hello World!"
  }
}
const anvilClient = new Anvil({ apiKey })
const { statusCode, data } = await anvilClient.fillPDF(pdfTemplateID, exampleData)

// By default, if the PDF has been published then the published version is what will
// be filled. If the PDF has not been published, then the most recent version will
// be filled. 
// 
// However, a version number can also be passed in that will be used retrieve and
// fill a specific version of the PDF.
// You can also use the constant `Anvil.VERSION_LATEST` (or `-1`) to fill the most 
// recent version of your PDF, whether that version has been published or not.
// Use this if you'd like to fill out a draft version of your template/PDF.
const options = { versionNumber: Anvil.VERSION_LATEST }
const { statusCode, data } = await anvilClient.fillPDF(pdfTemplateID, exampleData, options)

console.log(statusCode) // => 200

// Data will be the filled PDF raw bytes
fs.writeFileSync('output.pdf', data, { encoding: null })

API

Method names

Every method with an acronym in its name can also be called with the acronym in plain camel case, and vice versa. Use whichever you prefer; they are the same method.

fillPDFfillPdf
generatePDFgeneratePdf
generateEtchSignUrlgenerateEtchSignURL
requestGraphQLrequestGraphql or requestGraphQl
requestRESTrequestRest
prepareGraphQLFileprepareGraphqlFile or prepareGraphQlFile

The baseURL option is also accepted as baseUrl.

Instance Methods

new Anvil(options)

Creates an Anvil client instance.

  • options (Object) - Options for the Anvil Client instance.
const anvilClient = new Anvil({ apiKey: 'abc123' })

fillPDF(pdfTemplateID, payload[, options])

Fills a PDF template with your JSON data.

First, you will need to have uploaded a PDF to Anvil. You can find the PDF template's ID on PDF template's page:

PDF Template ID

If you already have the URL to the page in Anvil, the ID is there as well: https://app.useanvil.com/org/<your-org-slug>/pdf/<pdf-template-id>

An example:

const fs = require('fs')

// PDF template you uploaded to Anvil
const pdfTemplateID = 'kA6Da9CuGqUtc6QiBDRR'

// Your API key from your Anvil organization settings
const apiKey = '7j2JuUWmN4fGjBxsCltWaybHOEy3UEtt'

// JSON data to fill the PDF
const payload = {
  "title": "My PDF Title",
  "fontSize": 10,
  "textColor": "#CC0000",
  "data": {
    "someFieldId": "Hello World!"
  }
}
// The 'options' parameter is optional
const options = {
  "dataType": "buffer"
}
const anvilClient = new Anvil({ apiKey })
const { statusCode, data } = await anvilClient.fillPDF(pdfTemplateID, payload, options)

// Be sure to write the file as raw bytes
fs.writeFileSync('filled.pdf', data, { encoding: null })
  • pdfTemplateID (String) - The id of your PDF template from the Anvil UI
  • payload (Object) - The JSON data that will fill the PDF template
    • title (String) - optional Set the title encoded into the PDF document
    • fontSize (Number) - optional Set the fontSize of all filled text. Default is 10.
    • color (String) - optional Set the text color of all filled text. Default is dark blue.
    • data (Object) - The data to fill the PDF. The keys in this object will correspond to a field's ID in the PDF. These field IDs and their types are available on the PDF template's page in the Anvil dashboard.
      • For example { "someFieldId": "Hello World!" }
  • options (Object) - optional Any additional options for the request
    • dataType (Enum[String]) - optional Set the type of the data value that is returned in the resolved Promise. Defaults to 'buffer', but 'arrayBuffer' and 'stream' are also supported.
    • versionNumber (Number) - optional The version of the PDF template to fill. Defaults to the latest published version, or the latest version if the template has never been published. See Constants.
    • throwOnError (Boolean) - optional Overrides the client's throwOnError for this request.
  • Returns a Promise that resolves to an Object
    • statusCode (Number) - the HTTP status code; 200 is success
    • data (Buffer | ArrayBuffer | Stream) - The raw binary data of the filled PDF if success. Will be either a Buffer, ArrayBuffer, or a Stream, depending on dataType option supplied to the request.
    • errors (Array of Objects) - Will be present if status >= 400. See Errors
      • message (String)
generatePDF(payload[, options])

Dynamically generate a new PDF from your HTML and CSS or markdown.

Useful for agreements, invoices, disclosures, or any other text-heavy documents. This does not require you do anything in the Anvil UI other than setup your API key, just send it data, get a PDF. See the generate PDF docs for full details.

Check out our HTML invoice template for a complete HTML to PDF example.

An example:

const fs = require('fs')

// Your API key from your Anvil organization settings
const apiKey = '7j2JuUWmN4fGjBxsCltWaybHOEy3UEtt'

// An example using an HTML to PDF payload
const payload = {
  title: 'Example',
  type: 'html',
  data: {
    html: `
      <h1 class='header-one'>What is Lorem Ipsum?</h1>
      <p>
        Lorem Ipsum is simply dummy text...
      </p>
      <h3 class='header-two'>Where does it come from?</h3>
      <p>
        Contrary to popular belief, Lorem Ipsum is not simply <i>random text</i>
      </p>
    `,
    css: `
      body { font-size: 14px; color: #171717; }
      .header-one { text-decoration: underline; }
      .header-two { font-style: underline; }
    `,
  },
}

// An example using a Markdown payload
const payload = {
  title: 'Example Invoice',
  data: [{
    label: 'Name',
    content: 'Sally Jones',
  }, {
    content: 'Lorem **ipsum** dolor sit _amet_',
  }, {
    table: {
      firstRowHeaders: true,
      rows: [
        ['Description', 'Quantity', 'Price'],
        ['4x Large Widgets', '4', '$40.00'],
        ['10x Medium Sized Widgets in dark blue', '10', '$100.00'],
        ['10x Small Widgets in white', '6', '$60.00'],
      ],
    },
  }],
}
// The 'options' parameter is optional
const options = {
  "dataType": "buffer"
}
const anvilClient = new Anvil({ apiKey })
const { statusCode, data } = await anvilClient.generatePDF(payload, options)

// Be sure to write the file as raw bytes
fs.writeFileSync('generated.pdf', data, { encoding: null })
  • payload (Object) - The JSON data that will fill the PDF template
    • title (String) - optional Set the title encoded into the PDF document
    • data (Array of Objects) - The data that generates the PDF. See the docs for all supported objects
      • For example [{ "label": "Hello World!", "content": "Test" }]
  • options (Object) - optional Any additional options for the request
    • dataType (Enum[String]) - optional Set the type of the data value that is returned in the resolved Promise. Defaults to 'buffer', but 'arrayBuffer' and 'stream' are also supported.
    • throwOnError (Boolean) - optional Overrides the client's throwOnError for this request.
  • Returns a Promise that resolves to an Object
    • statusCode (Number) - the HTTP status code; 200 is success
    • data (Buffer | ArrayBuffer | Stream) - The raw binary data of the filled PDF if success. Will be either a Buffer, ArrayBuffer, or a Stream, depending on dataType option supplied to the request.
    • errors (Array of Objects) - Will be present if status >= 400. See Errors
      • message (String)
createEtchPacket(options)

Creates an Etch Packet and optionally sends it to the first signer.

const anvilClient = new Anvil({ apiKey })

const { statusCode, data, errors } = await anvilClient.createEtchPacket({
  variables: {
    name: 'Example packet',
    // Test packets use development signatures and are not billed
    isTest: true,
    // Send it to the first signer right away
    isDraft: false,
    files: [{
      id: 'uploadedNDA',
      title: 'Demo NDA',
      file: Anvil.prepareGraphQLFile('./nda.pdf'),
      fields: [{
        id: 'signatureOne',
        type: 'signature',
        pageNum: 0,
        rect: { x: 100, y: 100, width: 150, height: 30 },
      }],
    }],
    signers: [{
      id: 'signerOne',
      name: 'Sally Signer',
      email: '[email protected]',
      // 'embedded' signers sign in your app with a URL from generateEtchSignUrl.
      // The default, 'email', sends them an email.
      signerType: 'embedded',
      fields: [{ fileId: 'uploadedNDA', fieldId: 'signatureOne' }],
    }],
  },
})

const etchPacket = data.data.createEtchPacket
const signerEid = etchPacket.documentGroup.signers[0].eid
  • options (Object) - An object with the following structure:
    • variables (Object) - See the API Documentation area for details. See Examples area for examples.
    • responseQuery (String) - optional A GraphQL Query compliant query to use for the data desired in the mutation response. Can be left out to use default.
    • mutation (String) - optional If you'd like complete control of the GraphQL mutation, you can pass in a GraphQL Mutation compliant string that will be used in the mutation call. This string should also include your response query, as the responseQuery param is ignored if mutation is passed. Example:
        mutation CreateEtchPacket (
          $name: String,
          ...
        ) {
          createEtchPacket (
            name: $name,
            ...
          ) {
            id
            eid
            ...
          }
        }
      
getEtchPacket(options)

Gets the details of an Etch Packet.

const { statusCode, data, errors } = await anvilClient.getEtchPacket({
  variables: { eid: etchPacketEid },
})
const { status, documentGroup } = data.data.etchPacket
  • options (Object) - An object with the following structure:
    • variables (Object) - Requires eid
      • eid (String) - your Etch Packet eid
    • responseQuery (String) - optional A GraphQL Query compliant query to use for the data desired in the query response. Can be left out to use default.
generateEtchSignUrl(options)

Generates an Etch sign URL for an Etch Packet signer. The Etch Packet and its signers must have already been created, the packet must have been sent, and the signer must be an embedded signer.

const { statusCode, url, errors } = await anvilClient.generateEtchSignUrl({
  variables: {
    signerEid,
    // Your ID for the user who will be signing
    clientUserId: 'user-123',
  },
})
// Send your user to `url` to sign
  • options (Object) - An object with the following structure:
    • variables (Object) - Requires clientUserId and signerEid
      • clientUserId (String) - your ID for the user who will sign
      • signerEid (String) - the eid of the Etch Packet signer, found in the response of the createEtchPacket instance method
  • Returns a Promise that resolves to an Object
    • statusCode (Number) - the HTTP status code; 200 is success
    • url (String) - the signing URL
    • errors (Array of Objects) - Will be present if there was a problem. See Errors
downloadDocuments(documentGroupEid[, options])

Returns a Buffer, ArrayBuffer, or Stream of the document group specified by the documentGroupEid in Zip file format.

const { statusCode, data, errors } = await anvilClient.downloadDocuments(documentGroupEid)
fs.writeFileSync('documents.zip', data, { encoding: null })

// Or stream it
const { data: stream } = await anvilClient.downloadDocuments(documentGroupEid, { dataType: 'stream' })
stream.pipe(fs.createWriteStream('documents.zip'))
  • documentGroupEid (string) - the eid of the document group to download
  • options (Object) - optional Any additional options for the request
    • dataType (Enum[String]) - optional Set the type of the data value that is returned in the resolved Promise. Defaults to 'buffer', but 'arrayBuffer' and 'stream' are also supported.
    • throwOnError (Boolean) - optional Overrides the client's throwOnError for this request.
  • Returns a Promise that resolves to an Object
    • statusCode (Number) - the HTTP status code, 200 is success
    • response (Object) - the Response object resulting from the client's request to the Anvil app
    • data (Buffer | ArrayBuffer | Stream) - The raw binary data of the downloaded documents if success. Will be in the format of either a Buffer, ArrayBuffer, or a Stream, depending on dataType option supplied to the request.
    • errors (Array of Objects) - Will be present if status >= 400. See Errors
      • message (String)
forgeSubmit(options)

Creates or updates a submission to a webform in a Workflow, the same as someone filling in and submitting that webform. Webforms are called Forges in the API.

const { statusCode, data, errors } = await anvilClient.forgeSubmit({
  variables: {
    // The webform's eid
    forgeEid: 'your-forge-eid',
    // Keys are the IDs of the webform's fields
    payload: {
      name: 'Sally Jones',
      email: '[email protected]',
    },
    isTest: true,
  },
})
const submission = data.data.forgeSubmit
const weldDataEid = submission.weldData.eid
  • options (Object) - An object with the following structure:
    • variables (Object) - Requires forgeEid and payload. Pass weldDataEid and submissionEid to update an existing submission. See the GraphQL reference for all variables.
    • responseQuery (String) - optional A GraphQL Query compliant query to use for the data desired in the mutation response. Can be left out to use default.
    • mutation (String) - optional Your own complete GraphQL mutation. responseQuery is ignored if this is passed.
  • Returns a Promise that resolves to an Object with statusCode, data and errors, like requestGraphQL
removeWeldData(options)

Deletes a Workflow submission (a WeldData) and everything in it.

const { statusCode, data, errors } = await anvilClient.removeWeldData({
  variables: { eid: weldDataEid },
})
const removed = data.data.removeWeldData // true
  • options (Object) - An object with the following structure:
    • variables (Object) - Requires eid
      • eid (String) - the eid of the WeldData to remove
    • mutation (String) - optional Your own complete GraphQL mutation.
  • Returns a Promise that resolves to an Object with statusCode, data and errors, like requestGraphQL
requestGraphQL(queryInfo[, options])

A fallback function for queries and mutations without a specialized function in this client.

See the GraphQL reference for a listing on all possible queries

const result = await client.requestGraphQL({
  query: `
    query WeldDataQuery ($eid: String!) {
      weldData (eid: $eid) {
        eid
        isComplete
        isTest
      }
    }
  `,
  variables: { eid: 'nxflNZqxDUbltLUbYWK' },
})
const { statusCode, data, errors } = result

if (errors) {
  // GraphQL errors (with a 200 statusCode) or HTTP errors. See Errors.
} else {
  const weldData = data.data.weldData
}
  • queryInfo (Object)
    • query (String) - GraphQL query or mutation to run. See the GraphQL reference for a listing on all possible queries
    • variables (Object) - GraphQL variables for the query. Files to upload can be anywhere in here; see prepareGraphQLFile.
  • options (Object) - optional Any additional options for the request
    • throwOnError (Boolean) - optional Overrides the client's throwOnError for this request.
  • Returns a Promise that resolves to an Object
    • statusCode (Number) - The HTTP status code. GraphQL errors come back with a 200; you will only see other codes if the request itself was a problem, e.g. it was malformed or unauthorized.
    • data (Object) - Present when there are no errors
      • data (Object) - Contains the actual result of the query
        • [queryName] (Object) - Use the query or mutation name to reference the data that you requested!
    • errors (Array of Objects) - Present when there were GraphQL or HTTP errors. See Errors
requestREST(url, fetchOptions[, clientOptions])

A fallback function for REST endpoints without a specialized function in this client.

See the GraphQL reference for a listing on all possible queries

const result = await this.requestREST(
  `/api/v1/fill/${pdfTemplateID}.pdf`,
  {
    method: 'POST',
    body: JSON.stringify(payload),
    headers: {
      'Content-Type': 'application/json',
    },
  },
  {
    dataType: 'stream',
  },
)
  • url (String) - URL from the baseURL. e.g. /api/v1/fill
  • fetchOptions (Object) - Options passed to fetch. The client uses undici's fetch.
  • clientOptions (Object) - optional Any additional options for the request
    • dataType (Enum[String]) - optional Set the type of the data value that is returned in the resolved Promise. Defaults to 'buffer', 'arrayBuffer', 'stream', and 'json' are also supported.
    • throwOnError (Boolean) - optional Overrides the client's throwOnError for this request.
  • Returns a Promise that resolves to an Object
    • statusCode (Number) - the HTTP status code; 200 is success
    • data (Buffer | ArrayBuffer | Stream) - The raw binary data of the filled PDF if success. Will be either a Buffer, ArrayBuffer, or a Stream, depending on dataType option supplied to the request.
    • errors (Array of Objects) - Will be present if status >= 400. See Errors
      • message (String)

Class Methods

prepareGraphQLFile(pathOrStreamLikeThing[, options])

A nice helper to prepare a File upload for use with our GraphQL API. By default, this will upload your files as multipart uploads over the jaydenseric / GraphQL multipart request spec. Files given as a path or a stream are streamed to the API rather than read into memory first. If a request has to be retried (see Rate Limits), files given as a path, a file stream or a Buffer are sent again; any other stream can only be read once, so the retry fails with an error instead. NOTE: Please see below about certain scenarios where you may need to manually provide a filename.

  • pathOrSupportedInstance (String | File | Blob | Stream | Buffer) - Can be one of several things. Here's a list of what's supported, in order of preference:
    1. A File instance.
    2. A Blob instance.
    3. A string that is a path to a file to be used.
    4. A ReadStream instance that must either have either:
      1. A path property (this will usually be present when the stream was loaded from the local file system)
      2. A filename provided in the options parameter.
    5. A Buffer instance with a filename provided in the options parameter.
  • options (Object) - Options that may be required when providing certain types of values as the first parameter. For example, Blobs, Buffers and certain kinds of ReadStreams will not have any notion of what the name of the file is or should be when uploaded.
    • filename (String) - Override the filename of the uploaded file here. If providing a generic ReadStream or Buffer, you will be required to provide a filename here
    • mimetype (String) - Optional mimetype to specify with the resulting file that can be used when a string path, Buffer or ReadStream are provided as the first parameter.
  • Returns an Object that is properly formatted to be coerced by the client for use against our GraphQL API wherever an Upload type is required.

Options

Options for the Anvil Client. Defaults are shown after each option key.

{
  // One of apiKey or accessToken is required. See Authentication.
  apiKey: <your_api_key>,
  accessToken: <oauth_access_token>,

  // Where to send requests. See baseURL. Also accepted as baseUrl.
  baseURL: 'https://app.useanvil.com',

  // Sent as the User-Agent header
  userAgent: 'Anvil API Client/<version>',

  // How many requests to allow per requestLimitMS until the first response from
  // the API tells the client your actual limits. See Rate Limits.
  requestLimit: 1,
  requestLimitMS: 1000,

  // An undici Dispatcher to send requests with, e.g. to use a proxy:
  // new ProxyAgent('http://proxy.example.com:8080'). Defaults to the client's own Agent.
  dispatcher: <undici_dispatcher>,

  // Reject with an AnvilRequestError when the API responds with an error, rather
  // than resolve with `errors`. See throwOnError.
  throwOnError: false,
}
Authentication

Authenticate with either an API key or an OAuth access token.

API key: Find your API keys in your Anvil organization's settings. Requests use HTTP Basic auth: Basic base64(apiKey + ':').

const anvilClient = new Anvil({ apiKey: 'your-api-key' })

OAuth access token: Requests use Bearer auth. Anvil's access tokens are JWTs, which are sent as-is: Bearer <accessToken>. Any other kind of token is base64 encoded: Bearer base64(accessToken).

const anvilClient = new Anvil({ accessToken: 'your-oauth-access-token' })

If you pass both, the accessToken is used.

baseURL

By default, requests go to production, https://app.useanvil.com. Set baseURL to send them somewhere else. All requests go to that host:

  • REST: {baseURL}/api/v1/...
  • GraphQL: {baseURL}/graphql
  • Document downloads: {baseURL}/api/document-group/{eid}.zip
const anvilClient = new Anvil({
  apiKey: 'your-api-key',
  baseURL: 'https://anvil.example.com',
})

Don't include a trailing slash.

Constants

  • Anvil.VERSION_LATEST (-1) - With fillPDF's versionNumber option, fills the most recent version of a PDF template, published or not. Use it to fill drafts.
  • Anvil.VERSION_LATEST_PUBLISHED (-2) - Fills the latest published version. Unlike leaving versionNumber out, this returns an error if the template has never been published.

Errors

By default, the client's methods don't reject because of a problem response from the API. Instead, they resolve with an errors array alongside the statusCode. Each item has a message, a name, or both. To have them reject instead, turn on throwOnError.

const { statusCode, data, errors } = await anvilClient.fillPDF(pdfTemplateID, payload)
if (errors) {
  console.log(statusCode, errors)
}

errors looks like one of these:

  • GraphQL errors, as returned by the API, often with a 200 statusCode:
    [{ message: 'Not Found', path: ['etchPacket'], locations: [{ line: 2, column: 3 }] }]
    
  • A REST error. Validation errors have no message, but list each problem field:
    [{ name: 'AssertionError', message: 'PDF did not generate properly from given HTML!' }]
    [{ name: 'ValidationError', status: 400, fields: [{ message: 'Required', property: 'data' }] }]
    
  • A response that isn't JSON, like a 502 from a proxy. The name and message are the HTTP status text:
    [{ name: 'Bad Gateway', message: 'Bad Gateway' }]
    
  • A JSON response that couldn't be parsed. This is the parsing error's name, message, code, cause and stack:
    [{ name: 'SyntaxError', message: 'Unexpected end of JSON input', stack: '...' }]
    

The methods do reject when a request can't be made at all, e.g. because of a network error. These are fetch errors, with details in cause:

try {
  await anvilClient.fillPDF(pdfTemplateID, payload)
} catch (error) {
  console.log(error.message) // 'fetch failed'
  console.log(error.cause.code) // e.g. 'ECONNREFUSED'
}

If the connection closes before the whole response arrives, a method that returns binary data (like fillPDF or downloadDocuments) rejects with TypeError: terminated. One that returns JSON resolves with errors: [{ name: 'TypeError', message: 'terminated' }].

They also throw for invalid arguments, like an unsupported dataType.

throwOnError

Pass throwOnError: true and the methods reject with an AnvilRequestError whenever they would otherwise resolve with errors. That way one try/catch handles every kind of failure. It's off by default; with it off, the methods behave as described above.

const Anvil = require('@anvilco/anvil')
const { AnvilRequestError } = Anvil
// Or: import Anvil, { AnvilRequestError } from '@anvilco/anvil'

const anvilClient = new Anvil({ apiKey, throwOnError: true })

try {
  const { data } = await anvilClient.fillPDF(pdfTemplateID, payload)
  fs.writeFileSync('filled.pdf', data, { encoding: null })
} catch (error) {
  if (error instanceof AnvilRequestError) {
    console.log(error.message) // e.g. '400: ValidationError (data: Required)'
    console.log(error.statusCode, error.errors)
  } else {
    throw error // e.g. 'fetch failed'
  }
}

A successful request resolves just as it does with throwOnError off.

fillPDF, generatePDF, downloadDocuments, requestGraphQL and requestREST also accept throwOnError in their options, to override the client's setting for one request:

const { statusCode, errors } = await anvilClient.fillPDF(pdfTemplateID, payload, { throwOnError: false })

An AnvilRequestError has:

  • name (String) - 'AnvilRequestError'
  • message (String) - The status code, then each error's name and message, and a validation error's fields, separated by '; '. For example, '400: ValidationError (data: Required)' or '200: Not Found; Forbidden'.
  • statusCode (Number) - The HTTP status code. GraphQL errors usually come with a 200.
  • errors (Array of Objects) - The errors the method would have resolved with. See above.
  • data (Object) - Any data the API returned alongside the errors, otherwise undefined. A GraphQL request can partly succeed; this is the GraphQL response's data, so a query's result is at error.data.etchPacket, not data.data.etchPacket as when a request succeeds.
  • response (Response) - The fetch Response, e.g. for the rate limit headers. Its body has already been read.

Everything that rejects with throwOnError off still rejects the same way with it on: network errors, a binary response cut short (TypeError: terminated), and invalid arguments. A JSON response cut short, which would resolve with errors: [{ name: 'TypeError', message: 'terminated' }], rejects with an AnvilRequestError. A 429 is still retried, not thrown.

Rate Limits

Our API has request rate limits in place. The initial request made by this client will parse the limits for your account from the X-RateLimit-Limit and X-RateLimit-Interval-Ms response headers, and then handle the throttling of subsequent requests for you automatically. Any requests made while waiting for that first response will wait for it before being sent.

Like the API, the client counts requests over a sliding window: it only holds a request back when X-RateLimit-Limit requests have already been sent in the last X-RateLimit-Interval-Ms.

In the event that this client still receives a 429 Too Many Requests error response, it will wait for the number of seconds in the Retry-After header, plus 50ms, then retry the request.

See the Anvil API docs for more information on the specifics of the rate limits.

API Documentation

Our general API Documentation can be found here. It's the best resource for up-to-date information about our API and its capabilities.

See the PDF filling API docs for more information about the fillPDF method.

Examples

The examples folder has complete, runnable examples in JavaScript and TypeScript: filling a PDF, generating a PDF from Markdown or HTML, e-signing with a template or an uploaded PDF, and embedded signing with a download of the signed documents.

Development

See CONTRIBUTING.md for how to run the tests and publish a release.

版本与发布记录

@anvilco/anvil 共发布 44 个版本,首次发布于 2020/02/06;过去 12 个月发布了 4 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
4.2.0latest2026/11/034 天前
4.1.12026/10/309 天前
4.1.02026/10/2217 天前
4.0.02026/10/1523 天前
3.3.22025/03/101.7 年前
3.3.12024/10/052 年前

其他发布通道:beta → 3.2.0-beta.1。

关于 @anvilco/anvil 的常见问题

@anvilco/anvil 是什么?

Anvil API Client

如何安装 @anvilco/anvil?

在项目目录运行 npm install @anvilco/anvil,或使用 pnpm add / yarn add / bun add。

@anvilco/anvil 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@anvilco/anvil 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@anvilco/anvil 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@anvilco/anvil 可以商用吗?

@anvilco/anvil 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@anvilco/anvil 的下载量有多少?

根据 npm 官方下载统计,@anvilco/anvil 在 2026/10/06 – 2026/11/04 这 30 天内被下载 269,630 次,日均约 8,988 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@anvilco/anvil 还在维护吗?

最新版本 4.2.0 发布于 2026/11/03(4 天前);至今共发布 44 个版本,其中过去 12 个月发布了 4 个;首次发布于 2020/02/06。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。