Skip to content
SignWithDocs
Esc
  • Developer docs homeDocs
  • ChangelogDocs
  • OverviewREST API · Get started
  • Quick startREST API · Get started
  • Text tagsREST API · Get started
  • AuthenticationREST API · Get started
  • Making requestsREST API · Get started
  • ErrorsREST API · Get started

Billing API reference

Buy credits from the API. When a request fails with 402 insufficient_credits: GET /credit_packs → POST /checkouts → send the user the checkout_url → poll GET /checkouts/{id} until status is completed → retry the original request with the same Idempotency-Key. See Buying credits.

Updated

List credit packs

get/api/v1/credit_packs
  • Read-only keys can call this

Lists the credit packs that can be bought, cheapest first. Call it after a 402 insufficient_credits (or when GET /credits shows can_send: false) to show the user their options before starting a checkout. The lifetime deal (unlimited: true) is left out for users who already have it. Returns every pack in one page (has_more is always false).

Request sample

curl https://app.signwith.co/api/v1/credit_packs \
  -H "Authorization: Bearer $SIGNWITH_API_KEY"

Response 200

The credit packs on sale. Each item in data is a credit pack object.

JSON
{
  "object": "list",
  "data": [
    {
      "object": "credit_pack",
      "id": 3,
      "name": "Basic",
      "description": "10 documents",
      "credits": 10,
      "unlimited": false,
      "price": "9.00",
      "currency": "USD",
      "price_per_credit": "0.90"
    },
    {
      "object": "credit_pack",
      "id": 4,
      "name": "Pro",
      "description": "25 documents",
      "credits": 25,
      "unlimited": false,
      "price": "19.00",
      "currency": "USD",
      "price_per_credit": "0.76"
    },
    {
      "object": "credit_pack",
      "id": 5,
      "name": "Business",
      "description": "50 documents",
      "credits": 50,
      "unlimited": false,
      "price": "29.00",
      "currency": "USD",
      "price_per_credit": "0.58"
    },
    {
      "object": "credit_pack",
      "id": 9,
      "name": "Lifetime deal",
      "description": "Unlimited documents, forever",
      "credits": null,
      "unlimited": true,
      "price": "149.00",
      "currency": "USD",
      "price_per_credit": null
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Errors

401The API key is missing, invalid or expired, or its account has been archived. (unauthenticated, api_key_expired)
429Rate limit exceeded (rate_limited, with Retry-After); more than 20 POST /feedback messages in an hour (rate_limited, with Retry-After: seconds until the next clock hour); or — for POST /signature_requests/{id}/remind — signers were reminded less than an hour ago (remind_too_soon, with Retry-After). (rate_limited, remind_too_soon)
500Something went wrong on SignWith's side. It's safe to retry with the same Idempotency-Key. (internal_error)

Codes listed are the examples the spec gives; see all error codes.

Start a credit purchase

post/api/v1/checkouts

Starts buying a credit pack and returns a hosted checkout_url for the user to pay on. Payment happens entirely on the payment provider's page; credits are added once the provider confirms the payment. Poll GET /checkouts/{id} to find out when that happens, then retry the request that failed with 402 insufficient_credits.

An optional discount_code applies a discount if it is active, not used up and valid for the chosen pack (422 invalid_discount_code otherwise); the returned credit_pack then describes the discounted offer. Requires a full-access key and a billing country on the user's SignWith profile (422 billing_details_required). Buying the lifetime deal twice returns 422 already_purchased. If the payment provider can't start the payment the API returns 502 payment_provider_error.

Each call starts a new checkout, so send an Idempotency-Key to make retries safe.

Headers

  • Idempotency-Keystring

    A unique value (e.g. a UUID) that makes retries of this request safe. A successful response is stored for 24 hours and replayed for retries with the same key.

    at most 255 characters

Request body

  • credit_pack_idintegerRequired

    ID of a pack from GET /credit_packs.

  • discount_codestring

    Optional discount code for this pack.

Request sample

curl -X POST https://app.signwith.co/api/v1/checkouts \
  -H "Authorization: Bearer $SIGNWITH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "credit_pack_id": 4
}'
Example: With a discount code
curl -X POST https://app.signwith.co/api/v1/checkouts \
  -H "Authorization: Bearer $SIGNWITH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "credit_pack_id": 4,
  "discount_code": "LAUNCH20"
}'

Response 201

The checkout was started. Send the user to checkout_url. Returns a checkout object.

Example response: see the example object below.

Errors

400The request body is not valid JSON. (invalid_json)
401The API key is missing, invalid or expired, or its account has been archived. (unauthenticated, api_key_expired)
403The key is read-only, or its user can't access this resource. (read_only_api_key, forbidden)
404The resource doesn't exist or belongs to another account. (not_found)
409A request with the same Idempotency-Key is still being processed. Retry shortly. (idempotency_key_in_use)
422The request is valid JSON but can't be processed. See error.code; the codes each endpoint can return are described in the endpoint's description and in the error table above. (missing_parameter, invalid_parameter, invalid_request, missing_documents, too_many_documents, document_too_large, invalid_document, invalid_file_type, pdf_encrypted, missing_signers, invalid_signer, already_completed, not_open, nothing_to_remind, invalid_field, template_has_no_fields, template_archived, signer_finished, idempotency_key_reused, invalid_discount_code, already_purchased, billing_details_required)
429Rate limit exceeded (rate_limited, with Retry-After); more than 20 POST /feedback messages in an hour (rate_limited, with Retry-After: seconds until the next clock hour); or — for POST /signature_requests/{id}/remind — signers were reminded less than an hour ago (remind_too_soon, with Retry-After). (rate_limited, remind_too_soon)
500Something went wrong on SignWith's side. It's safe to retry with the same Idempotency-Key. (internal_error)
502The payment provider couldn't start the checkout. Nothing was charged; try again later. (payment_provider_error)

Codes listed are the examples the spec gives; see all error codes.

Get a checkout

get/api/v1/checkouts/{id}
  • Read-only keys can call this

Returns a checkout's status. Poll it after sending the user the checkout_url: pending means the user hasn't paid yet (or the payment is still being confirmed), completed means the credits have been added, and failed means the payment failed or was canceled — start a new checkout to try again. checkout_url is only returned while the checkout is pending. Only checkouts started by the key's user can be read.

Path parameters

  • idstring (uuid)Required

    Checkout ID (the id returned by POST /checkouts).

Request sample

curl https://app.signwith.co/api/v1/checkouts/3f6b2c1a-8d4e-4b7a-9c20-5e1f7a9d3b84 \
  -H "Authorization: Bearer $SIGNWITH_API_KEY"

Response 200

The checkout. Returns a checkout object.

JSON
{
  "object": "checkout",
  "id": "3f6b2c1a-8d4e-4b7a-9c20-5e1f7a9d3b84",
  "status": "completed",
  "credit_pack": {
    "object": "credit_pack",
    "id": 4,
    "name": "Business",
    "description": "50 documents",
    "credits": 50,
    "unlimited": false,
    "price": "29.00",
    "currency": "USD",
    "price_per_credit": "0.58"
  },
  "amount": "29.00",
  "currency": "USD",
  "discount_code": null,
  "checkout_url": null,
  "completed_at": "2026-09-27T11:24:09Z",
  "created_at": "2026-09-27T11:20:31Z"
}

Example response: see the example object below.

Errors

401The API key is missing, invalid or expired, or its account has been archived. (unauthenticated, api_key_expired)
404The resource doesn't exist or belongs to another account. (not_found)
429Rate limit exceeded (rate_limited, with Retry-After); more than 20 POST /feedback messages in an hour (rate_limited, with Retry-After: seconds until the next clock hour); or — for POST /signature_requests/{id}/remind — signers were reminded less than an hour ago (remind_too_soon, with Retry-After). (rate_limited, remind_too_soon)
500Something went wrong on SignWith's side. It's safe to retry with the same Idempotency-Key. (internal_error)

Codes listed are the examples the spec gives; see all error codes.

The credit pack object

A pack of credits that can be bought with POST /checkouts.

  • objectstringRequired
    Always credit_pack
  • idintegerRequired
  • namestringRequired
  • descriptionstring or nullRequired
  • creditsinteger or nullRequired

    Credits added when bought. null for the lifetime deal.

  • unlimitedbooleanRequired

    true for the lifetime deal, which removes the credit limit.

  • pricestringRequired

    Price as a decimal string with two places.

  • currencystringRequired
    Always USD
  • price_per_creditstring or nullRequired

    price divided by credits, as a decimal string. null for the lifetime deal.

The checkout object

A credit purchase started with POST /checkouts.

  • objectstringRequired
    Always checkout
  • idstring (uuid)Required

    Checkout ID. Use it with GET /checkouts/{id}.

  • statusstringRequired

    pending — waiting for payment or confirmation; completed — paid and credits added; failed — the payment failed or was canceled.

    One of pending, completed, failed
  • credit_packobjectRequired

    The pack being bought (the discounted offer when a discount_code was applied).

    9 child fields
    • objectstringRequired
      Always credit_pack
    • idintegerRequired
    • namestringRequired
    • descriptionstring or nullRequired
    • creditsinteger or nullRequired

      Credits added when bought. null for the lifetime deal.

    • unlimitedbooleanRequired

      true for the lifetime deal, which removes the credit limit.

    • pricestringRequired

      Price as a decimal string with two places.

    • currencystringRequired
      Always USD
    • price_per_creditstring or nullRequired

      price divided by credits, as a decimal string. null for the lifetime deal.

  • amountstringRequired

    Amount charged, as a decimal string.

  • currencystringRequired
    Always USD
  • discount_codestring or nullRequired
  • checkout_urlstring or nullRequired

    Hosted payment page to send the user to. Only present while status is pending.

    URL
  • completed_atstring (date-time) or nullRequired

    When the payment was confirmed.

  • created_atstring (date-time)Required
JSON
{
  "object": "checkout",
  "id": "3f6b2c1a-8d4e-4b7a-9c20-5e1f7a9d3b84",
  "status": "pending",
  "credit_pack": {
    "object": "credit_pack",
    "id": 4,
    "name": "Business",
    "description": "50 documents",
    "credits": 50,
    "unlimited": false,
    "price": "29.00",
    "currency": "USD",
    "price_per_credit": "0.58"
  },
  "amount": "29.00",
  "currency": "USD",
  "discount_code": null,
  "checkout_url": "https://checkout.dodopayments.com/buy/pl_2x7Kq9mTbR4vLw8N",
  "completed_at": null,
  "created_at": "2026-09-27T11:20:31Z"
}

Base URL, pagination and idempotency work the same on every endpoint: see making requests. Every error code is on errors.

Start building

The API and MCP server come with every account. 3 free documents a month, then pay per document.