NAV

Introduction

Welcome to the PaperOS API!

You can view code examples in the dark area to the right, and you can switch the programming language of the examples with the tabs in the top right.

This example API documentation page was created with DocuAPI, a multilingual documentation theme for the static site generator Hugo Extended Edition.

Prerequisites

Install the tools used in the examples

curl https://webi.sh/jq@1 | sh
curl https://webi.sh/node@lts | sh

source ~/.local/envman/PATH.sh

Please install jq and node to follow along in the examples.

API Base URL

The Base URL will be in the form of

https://app.paperos.com
https://example.c.paperos.com
https://paperos.example.com
https://demo.example.c.paperos.net

Use example.c.paperos.dev (note: .dev) for the live examples:

export PAPEROS_BASE_URL='https://example.c.paperos.dev'
var paperBase = process.env.PAPEROS_BASE_URL;

Depending on your account, your PaperOS base URL may resemble any of the following:

For general operations you can always use https://app.paperos.com, however, using the branded domain may be required for certain actions, such as those that generate branded notifications.

Your Sandbox base URL may look something like https://demo.example.c.paperos.net.

To-Dos

There are multiple things that need to have public-facing IDs and slugs and name changes:

OIDC Client

Our goal is Just Works™ compatibility with OIDC clients for:

However, as the spec is large and many pieces are sections have been superseded by other methodologies or unadopted in industry, we implement functionality as needed.

Please report any incompatibility with compliant clients to support as a bug.

Registration

In general, you should contact support to create and configure your OIDC Client.

Although you can create your own client with your user Access Token, it will have minimal permissions and may not be useful.

POST /api/v1/oidc/clients

curl --fail-with-body -X POST "${PAPEROS_BASE_URL}/api/v1/oidc/clients" \
    -H "Authorization: Bearer ${UI_ACCESS_TOKEN}" \
    -H 'Content-Type: application/json' \
    -d '{
           "redirect_uris": ["https://app.example.com/callback", "https://app.example.com/return"],
           "response_types": ["code"],
           "grant_types": ["authorization_code"],
           "contacts": ["devops@example.com"],
           "client_name": "Example App",
           "logo_uri": "https://app.example.com/logo.png",
           "client_uri": "https://app.example.com",
           "policy_uri": "https://app.example.com/privacy",
           "tos_uri": "https://app.example.com/terms",
           "sector_identifier_uri": "https://example.com/allowed_redirects.json",
           "initiate_login_uri": "https://app.example.com/oidc/start"
        }'
var data = {
   redirect_uris: [
      "https://app.example.com/callback",
      "https://app.example.com/return",
   ],
   response_types: ["code"],
   grant_types: ["authorization_code"],
   contacts: ["devops@example.com"],
   client_name: "Example App",
   logo_uri: "https://app.example.com/logo.png",
   client_uri: "https://app.example.com",
   policy_uri: "https://app.example.com/privacy",
   tos_uri: "https://app.example.com/terms",
   sector_identifier_uri: "https://example.com/allowed_redirects.json",
   initiate_login_uri: "https://app.example.com/oidc/start",
};
var payload = JSON.stringify(data, null, 2);

var url = `${paperBase}/api/v1/oidc/clients`;
var resp = await fetch(url, {
   method: "POST",
   headers: {
      Authorization: `Bearer ${uiAccessToken}`,
      "Content-Type": "application/json",
   },
   body: payload,
});
var oidcClient = await resp.json();

console.log(oidcClient);

Example Response:

{
   "rp_id": "ocrp_zdcw9paqtecqby8c",
   "sector_identifier": "a468aa0d-97ff-4b92-849e-63fe7f6b1817",
   "redirect_uris": [
      "https://app.example.com/callback",
      "https://app.example.com/return"
   ],
   "response_types": ["code"],
   "grant_types": ["authorization_code"],
   "application_type": "web",
   "contacts": ["devops@example.com"],
   "client_name": "Example App",
   "logo_uri": "https://app.example.com/logo.png",
   "client_uri": "https://app.example.com",
   "policy_uri": "https://app.example.com/privacy",
   "tos_uri": "https://app.example.com/terms",
   "jwks_uri": null,
   "subject_type": "pairwise",
   "token_endpoint_auth_method": "client_secret_basic",
   "default_max_age": 0,
   "require_auth_time": true,
   "initiate_login_uri": "https://app.example.com/oidc/start",
   "requestable_scopes": ["profile"],
   "client_secret": "86fa1382-055c-492d-9f0b-7fc0963ca0c7",
   "client_secret_expires_at": 0,
   "client_id": "oidc_pc2tsa5rd1m4tamb",
   "client_id_issued_at": 1745559634
}

Caveats:

For more information on the meaning of the various fields, and additional available fields, see the Client Metadata glossary:

List

GET /api/v1/oidc/clients

curl --fail-with-body "${PAPEROS_BASE_URL}/api/v1/oidc/clients" \
    -H "Authorization: Bearer ${UI_ACCESS_TOKEN}"
var url = `${paperBase}/api/v1/oidc/clients`;
var resp = await fetch(url, {
   headers: { Authorization: `Bearer ${uiAccessToken}` },
});
var oidcClients = await resp.json();

console.log(oidcClients);

Example Response:

{
   "success": true,
   "total": 1,
   "type": "[]<oidc_client>",
   "oidc_clients": [
      {
         "client_id": "oidc_zdcw93k7h4w4zb2m",
         "rp_id": "ocrp_zdcw9paqtecqby8c",
         "org_id": "org_01he3t2sjcs6zybd2cemyvn7qn",
         "external_id": null,
         "sector_identifier": "15e4cd2d-22e8-4f47-b9ca-8b80d7b4a870",
         "redirect_uris": [
            "https://app.example.com/callback",
            "https://app.example.com/return"
         ],
         "response_types": ["code"],
         "grant_types": ["authorization_code"],
         "application_type": "web",
         "contacts": ["devops@example.com"],
         "client_name": "Example App",
         "logo_uri": "https://app.example.com/logo.png",
         "client_uri": "https://app.example.com",
         "policy_uri": "https://app.example.com/privacy",
         "tos_uri": "https://app.example.com/terms",
         "jwks_uri": null,
         "sector_identifier_uri": null,
         "subject_type": "pairwise",
         "token_endpoint_auth_method": "client_secret_basic",
         "default_max_age": 0,
         "require_auth_time": true,
         "initiate_login_uri": "https://app.example.com/oidc/start",
         "requestable_scopes": ["profile", "impersonation"],
         "client_id_issued_at": 1745455434,
         "secrets": [
            {
               "id": "occs_zdcw9631vkaf4ppk",
               "secret": "9ba82b72-38eb-4a6d-bc57-bace7a0d5bd1",
               "comment": "",
               "expires_at": null,
               "created_at": "2025-04-23T18:43:54Z",
               "revoked_at": null
            }
         ],
         "client_secret_expires_at": 0,
         "client_secret": "9ba82b72-38eb-4a6d-bc57-bace7a0d5bd1"
      }
   ],
   "count": 1
}

Extensions:

Create Subject (User)

POST /api/v1/integrations/users

curl -v --fail-with-body -X POST "${PAPEROS_BASE_URL}/api/v1/integrations/users" \
    --user "${CLIENT_ID}:${CLIENT_SECRET}" \
    -H 'Content-Type: application/json' \
    -d '{
           "external_id": "john_doe-101",
           "email": "john.doe+101@example.com",
           "email_verified": false,
           "given_name": "John",
           "family_name": "Doe",
           "zoneinfo": "America/Denver",
           "locale": "en-US"
        }' | jq
var intlData = Intl.DateTimeFormat().resolvedOptions();
var data = {
   external_id: "john_doe-101",
   email: "john.doe+101@example.com",
   email_verified: false,
   given_name: "John",
   family_name: "Doe",
   zoneinfo: intlData.timeZone,
   locale: intlData.locale,
};
var payload = JSON.stringify(data, null, 2);

var url = `${PAPEROS_BASE_URL}/api/v1/integrations/users`;
var basicAuth = btoa(`${oidc.client_id}:${oidc.client_secret}`);
var resp = await fetch(url, {
   method: "POST",
   credentials: "include",
   headers: {
      Authorization: `Basic ${basicAuth}`,
      "Content-Type": "application/json",
   },
   body: payload,
});

var subjectResp = await resp.json();
console.log(subjectResp);

Example Result:

{
   "success": true,
   "type": "<oidc_subject>",
   "subject": {
      "oidc_client_id": "oidc_zdcw93k7h4w4zb2m",
      "sub": "sub_159rjy10v4qpfr0d",
      "external_id": "john_doe-101",
      "first_name": "John",
      "last_name": "Doe",
      "email": "john.doe+101@example.com",
      "phone": null,
      "zoneinfo": "America/Denver",
      "locale": "en-US",
      "picture": null,
      "granted_scopes": ["profile", "impersonation"],
      "orgs": [],
      "created_at": "2025-04-25 08:31:23Z",
      "updated_at": "2025-04-25 08:31:23Z"
   }
}

Update Subject (User)

Set the user's externalId as well as their current locale and zoneinfo.

This is primarily for the initial user sync.

PATCH /api/v1/integrations/users/:sub

b_ppid_sub='sub_xxxxxxxx'
b_our_id='100-john_doe'

curl -v --fail-with-body -X PATCH "${PAPEROS_BASE_URL}/api/v1/integrations/users/${b_ppid_sub}" \
    --user "${CLIENT_ID}:${CLIENT_SECRET}" \
    -H 'Content-Type: application/json' \
    -d '{
           "external_id": "'"${b_our_id}"'",
           "locale": "en-US",
           "zoneinfo": "America/Denver"
        }' | jq
var ppidSub = "sub_xxxxxxxx";
var ourId = "100-john_doe";

var intlData = Intl.DateTimeFormat().resolvedOptions();
var data = {
   external_id: ourId,
   locale: intlData.locale,
   zoneinfo: intlData.timeZone,
};
var payload = JSON.stringify(data, null, 2);

var url = `${PAPEROS_BASE_URL}/api/v1/integrations/users/${ppidSub}`;
var basicAuth = btoa(`${oidc.client_id}:${oidc.client_secret}`);
var resp = await fetch(url, {
   method: "PATCH",
   credentials: "include",
   headers: {
      Authorization: `Basic ${basicAuth}`,
      "Content-Type": "application/json",
   },
   body: payload,
});

console.log("OK:", resp.status, resp.ok);

Example Result:

204 NO CONTENT

List Subjects (Users)

GET /api/v1/integrations/users

curl --fail-with-body "${PAPEROS_BASE_URL}/api/v1/integrations/users" \
    --user "${CLIENT_ID}:${CLIENT_SECRET}" |
    jq
var url = `${PAPEROS_BASE_URL}/api/v1/integrations/users`;
var basicAuth = btoa(`${oidc.client_id}:${oidc.client_secret}`);
var resp = await fetch(url, {
   credentials: "include",
   headers: { Authorization: `Basic ${basicAuth}` },
});

Example Response:

{
   "success": true,
   "type": "<[]oidc_subject>",
   "subjects": [
      {
         "given_name": "John",
         "family_name": "Doe",
         "locale": "en-US",
         "zoneinfo": "America/Denver",
         "external_id": "john_doe-101",
         "sub": "sub_d0255a8r3hknnzsh",
         "granted_scopes": ["profile", "impersonation"],
         "orgs": [
            {
               "id": "org_ew421jr4pmnhs02z",
               "name": "My For Profit OIDC Client Test Co"
            }
         ]
      }
   ]
}

Authentication

Access Tokens can be created with your OIDC ID and Secret.

Set Organization-Scoped Access Token

export OIDC_ACCESS_TOKEN='xxxx.yyyy.zzzz'
var paperToken = process.env.OIDC_ACCESS_TOKEN;

PaperOS uses API keys to allow access to the API.

All API requests should include the API token in the Authorization header with the Bearer prefix:

Authorization: Bearer <token>

You can register a new PaperOS API key at our developer portal.

Create ID Token

The ID Token represents the subject (user) and can be used to create an organization.

POST /api/v1/integrations/id-token

curl -v --fail-with-body -X POST "${PAPEROS_BASE_URL}/api/v1/integrations/id-token" \
    --user "${CLIENT_ID}:${CLIENT_SECRET}" \
    -H "Content-Type: application/json" \
    -d '{
            "claims": {
                "external_id": { "value": "john_doe-101" },
                "auth_time": { "value": 1745571774 },
                "exp": { "value": 1745575374 },
                "amr": { "values": ["pwd"] }
            }
        }' | jq
var payload = JSON.stringify(
   {
      claims: {
         external_id: { value: "john_doe-101" },
         auth_time: { value: 1745571774 },
         exp: { value: 1745575374 },
         amr: { values: ["pwd"] },
      },
   },
   null,
   2,
);

var url = `${PAPEROS_BASE_URL}/api/v1/integrations/id-token`;
var basicAuth = btoa(`${oidc.client_id}:${oidc.client_secret}`);
var resp = await fetch(url, {
   method: "POST",
   credentials: "include",
   headers: {
      Authorization: `Basic ${basicAuth}`,
      "Content-Type": "application/json",
   },
   body: payload,
});

var accessTokenResult = await resp.json();
console.log(accessTokenResult);

Example Response:

{
   "id_token": "eyJ0eXAiOiJKV1QiLCJraWQiOiJTcG5TVEkyc1p3d0p3aV9oWVJ4VFFnOGlJZHdBMS0zUG81c1NsVUptUXdjIiwiYWxnIjoiRVMyNTYifQ.eyJqdGkiOiJkWXZfZHduTWtIajd3ZjNaM0FQbHZ3IiwiaXNzIjoiaHR0cHM6Ly9wYXBlcm9zLWRldi03LjExMDQuYy5ibm5hLm5ldCIsInN1YiI6InN1Yl9kMDI1NWE4cjNoa25uenNoIiwiYXV0aF90aW1lIjoxNzQ1NDQ3NzQ4LCJjbGllbnRfaWQiOiJvaWRjX3pkY3c5M2s3aDR3NHpiMm0iLCJlbWFpbCI6ImpvaG4uZG9lKzEwMUBleGFtcGxlLmNvbSIsImdpdmVuX25hbWUiOiJKb2huIiwiZmFtaWx5X25hbWUiOiJEb2UiLCJvcmdfaWQiOiJvcmdfZXc0MjFqcjRwbW5oczAyeiIsImlhdCI6MTc0NTQ1MDE0OSwiZXhwIjoxNzQ1NDUzNzQ5fQ.YuvVXJcwfsBJT00SzOHuphh1pt8KXJEMoPmZxKJJWCDrDPbrcI2vX6YNcKtgLOVcv7lKMK1-YKvJBNgAF_N-iA"
}

Create Access Token

The ID Token represents access to a specific organization through the subject (user).

POST /api/v1/integrations/access-token

curl -v --fail-with-body -X POST "${PAPEROS_BASE_URL}/api/v1/integrations/access-token" \
    --user "${CLIENT_ID}:${CLIENT_SECRET}" \
    -H "Content-Type: application/json" \
    -d '{
            "claims": {
                "external_id": { "value": "john_doe-101" },
                "org_id": { "value": "org_ew421jr4pmnhs02z" },
                "auth_time": { "value": 1745571774 },
                "exp": { "value": 1745575374 },
                "amr": { "values": ["pwd"] }
            }
        }' | jq
var payload = JSON.stringify(
   {
      claims: {
         external_id: { value: "john_doe-101" },
         org_id: { value: "org_ew421jr4pmnhs02z" },
         auth_time: { value: 1745571774 },
         exp: { value: 1745575374 },
         amr: { values: ["pwd"] },
      },
   },
   null,
   2,
);

var url = `${PAPEROS_BASE_URL}/api/v1/integrations/access-token`;
var basicAuth = btoa(`${oidc.client_id}:${oidc.client_secret}`);
var resp = await fetch(url, {
   method: "POST",
   credentials: "include",
   headers: {
      Authorization: `Basic ${basicAuth}`,
      "Content-Type": "application/json",
   },
   body: payload,
});

var accessTokenResult = await resp.json();
console.log(accessTokenResult);

Example Response:

{
   "access_token": "eyJ0eXAiOiJKV1QiLCJraWQiOiJTcG5TVEkyc1p3d0p3aV9oWVJ4VFFnOGlJZHdBMS0zUG81c1NsVUptUXdjIiwiYWxnIjoiRVMyNTYifQ.eyJqdGkiOiJkWXZfZHduTWtIajd3ZjNaM0FQbHZ3IiwiaXNzIjoiaHR0cHM6Ly9wYXBlcm9zLWRldi03LjExMDQuYy5ibm5hLm5ldCIsInN1YiI6InN1Yl9kMDI1NWE4cjNoa25uenNoIiwiYXV0aF90aW1lIjoxNzQ1NDQ3NzQ4LCJjbGllbnRfaWQiOiJvaWRjX3pkY3c5M2s3aDR3NHpiMm0iLCJlbWFpbCI6ImpvaG4uZG9lKzEwMUBleGFtcGxlLmNvbSIsImdpdmVuX25hbWUiOiJKb2huIiwiZmFtaWx5X25hbWUiOiJEb2UiLCJvcmdfaWQiOiJvcmdfZXc0MjFqcjRwbW5oczAyeiIsImlhdCI6MTc0NTQ1MDE0OSwiZXhwIjoxNzQ1NDUzNzQ5fQ.YuvVXJcwfsBJT00SzOHuphh1pt8KXJEMoPmZxKJJWCDrDPbrcI2vX6YNcKtgLOVcv7lKMK1-YKvJBNgAF_N-iA"
}

Inspect Token

GET /api/user/debug

curl --fail-with-body "${PAPEROS_BASE_URL}/api/user/debug" \
  -H "Authorization: Bearer ${OIDC_ACCESS_TOKEN}" |
  jq
var url = `${paperBase}/api/user/debug`;
var resp = await fetch(url, {
   headers: { Authorization: `Bearer ${paperToken}` },
});
var data = await resp.json();

Example Response:

{
   "user": {
      "account_id": null,
      "partner_id": null,
      "auth_time": "2023-09-19T22:25:54.000Z",
      "iat": "2023-09-19T22:25:54.000Z",
      "exp": null,
      "api_token": true,
      "email": "services+test1@savvi.legal"
   },
   "method": "GET",
   "originalUrl": "/api/user/debug"
}

Use the user and account debug endpoints to inspect details of the token.

Organizations

List (v1-draft)

GET /api/v1/orgs?updated_since=0

my_orgs="$(

    curl "${PAPEROS_BASE_URL}/api/v1/orgs?updated_since=0" \
        -H "Authorization: Bearer ${OIDC_ID_TOKEN}"

)"
echo "${my_orgs}" |
    jq
var url = `${paperBase}/api/v1/orgs?updated_since=0`;
var resp = await fetch(url, {
   headers: { Authorization: `Bearer ${paperToken}` },
});
var orgs = await resp.json();

console.log(orgs);

Example Response:

{
   "updated_at": 1677000469,
   "orgs": [
      {
         "id": "org_01ewdxxpvgg2y19pbtbyddtvv8",
         "name": "Test 1",
         "brand_id": "brand_00000000000000000000000000",
         "created_at": "2021-01-19T18:18:46.000Z",
         "updated_at": "2023-02-21T17:27:49.000Z"
      }
   ]
}

Retrieve all orgs associated with this user, including through direct ownership, delegation, or partnerships.

Query Parameters

Parameter Default Description
updated_since '' required, pass 0 or the previous updated_since

View Token Debug Info

GET /api/v1/org/debug

my_org_id="$(
    echo "${my_orgs}" |
        jq '.[0].id'
)"
echo "${my_org_id}"

curl "${PAPEROS_BASE_URL}/api/v1/org/debug?account_id=${my_org_id}" \
  -H "Authorization: Bearer ${OIDC_ID_TOKEN}" |
  jq
var orgId = orgs[0].id;
var url = `${paperBase}/api/v1/org/debug?account_id=${orgId}`;
var resp = await fetch(url, {
   headers: { Authorization: `Bearer ${paperToken}` },
});
var orgInfo = await resp.json();

Example Response:

{
   "user": {
      "account_id": 97,
      "partner_id": null,
      "auth_time": "2023-09-19T22:25:54.000Z",
      "iat": "2023-09-19T22:25:54.000Z",
      "exp": null,
      "api_token": true,
      "email": "services+test1@savvi.legal"
   },
   "method": "GET",
   "originalUrl": "/api/v1/org/debug?account_id=97"
}

Show account token details

Query Parameters

Parameter Default Description
account_id '' Either required or disallowed, based on token type.

Create (v1-draft)

POST /api/v1/orgs

curl "${PAPEROS_BASE_URL}/api/v1/orgs" \
    -X 'POST' \
    -H "Authorization: Bearer ${OIDC_ID_TOKEN}" \
    -H 'Content-Type: application/json' \
    --data-raw '{
        "name": "My Test Company 11",
        "fields": {
            "business_type": "for_profit"
        }
    }' |
    jq
var data = {
   name: "My Test Company 11",
   fields: {
      business_type: "for_profit",
   },
};
var payload = JSON.stringify(data, null, 2);

var url = `${paperBase}/api/v1/orgs`;
var resp = await fetch(url, {
   method: "POST",
   headers: {
      Authorization: `Bearer ${paperToken}`,
      "Content-Type": "application/json",
   },
   body: payload,
});
var orgInfo = await resp.json();

Example Response:

{
   "id": "org_01hbsvp9tk3qthd2jjz2vzv0g8",
   "name": "My Test Company 11",
   "brand_id": "brand_01h2stkn1fqe8dcfmyrq7thpab",
   "created_at": "2023-10-03T04:10:43.000Z",
   "updated_at": "2023-10-03T04:10:49.000Z"
}

Create a new organization.

Options for business_type are:

Records

Records are the collections of data that can be both the result of form submissions, and made available for autofill selection to future forms.

Create One

POST /api/v1/orgs/:org_id/records

curl "${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/records" \
    -X 'POST' \
    -H "Authorization: Bearer ${OIDC_ACCESS_TOKEN}" \
    -H 'Content-Type: application/json' \
    --data-raw '{
      "type": "indv",
      "name": "Jane Doe",
      "fields": {
        "title": "master painter",
        "email": "jane@jane.doe",
        "is_board_director": "1"
      }
    }' |
    jq
var data = {
   type: "indv",
   name: "Jane Doe",
   fields: {
      title: "master painter",
      email: "jane@jane.doe",
      is_board_director: "1",
   },
};
var payload = JSON.stringify(data, null, 2);

var url = `${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/records`;
var resp = await fetch(url, {
   method: "POST",
   headers: {
      Authorization: `Bearer ${OIDC_ACCESS_TOKEN}`,
      "Content-Type": "application/json",
   },
   body: payload,
});
var recordInfo = await resp.json();

Example Response:

{
   "success": true,
   "type": "string",
   "rec_id": "rec_01hcey7qcfeeqmh1af6x3xafa2"
}

Create a new record belonging to this organization.

POST /api/v1/orgs/{my_org_id}/records

Record Types

Below is a small subset of our record types, reach out for a tailored list fitting your needs.

Slug Code
activity acty
annual_report ann_rpt
contract k
doc doc
equity eq
equity_class eq_cl
financing fin
individual indv
investment invt
ip ip
legal_audit lgl_adt
org org
pii pii
questionnaire qre
state st
task task
tax_filing tax
tos tos

List All by Type

GET /api/v1/orgs/{org_id}/records?type={type_slug}

curl -G "${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/records" \
  --data-urlencode "type=org" \
  -H "Authorization: Bearer ${OIDC_ACCESS_TOKEN}" |
  jq
var params = { type: "org" };
var search = new URLSearchParams(params).toString();
var url = `${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/records?${search}`;
var resp = await fetch(url, {
   headers: {
      Authorization: `Bearer ${OIDC_ACCESS_TOKEN}`,
   },
});
var resInfos = await resp.json();

Example Response:

[
   {
      "id": 5617,
      "name": "Test 1",
      "resource_type_id": 2,
      "account_id": 97,
      "created_at": "2021-01-19T18:18:49.000Z",
      "updated_at": "2021-01-19T18:18:49.000Z",
      "finalized": 0,
      "archived": 0,
      "is_draft": 0,
      "features": {
         "name": "Test 1"
      }
   },
   {
      "id": 17413,
      "name": "Bob the Builder",
      "resource_type_id": 1,
      "account_id": 97,
      "created_at": "2023-09-22T19:50:13.000Z",
      "updated_at": "2023-09-22T19:50:13.000Z",
      "finalized": 0,
      "archived": 0,
      "is_draft": 0,
      "features": {
         "name": "Bob the Builder",
         "signatory_name": "Bob the Builder",
         "first_name": "Bob",
         "last_name": "Builder",
         "middle_name": "the",
         "title": "master builder",
         "email": "bob@bobbuild.bob",
         "employee_documents_list": "All of the above",
         "upload_or_generate": "Generate"
      }
   }
]

Records are scoped to a specific account.

TODO don't allow creating completely empty entities

Query Description
type the record type slug, such as individual or org (* for any)
rec_id a single record ids (begins with rec_)
rec_ids a comma-separated list of record ids (begin with rec_)
since an ISO timestamp of the last record received (second resolution)
limit return only n records

Get One by ID

GET /api/v1/orgs/:org_id/records/:rec_id

my_rec_id='17413'

curl "${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/records/${my_rec_id}" \
  -H "Authorization: Bearer ${OIDC_ACCESS_TOKEN}" |
  jq
var myRecId = "17413";

var url = `${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/records/${myRecId}`;
var resp = await fetch(url, {
   headers: {
      Authorization: `Bearer ${OIDC_ACCESS_TOKEN}`,
   },
});
var recordInfo = await resp.json();

Example Response:

{
   "id": 17413,
   "name": "Bob the Builder",
   "resource_type_id": 1,
   "account_id": 97,
   "created_at": "2023-09-22T19:50:13.000Z",
   "updated_at": "2023-09-22T19:50:13.000Z",
   "finalized": 0,
   "archived": 0,
   "is_draft": 0,
   "features": {
      "name": "Bob the Builder",
      "signatory_name": "Bob the Builder",
      "first_name": "Bob",
      "last_name": "Builder",
      "middle_name": "the",
      "title": "master builder",
      "email": "bob@bobbuild.bob",
      "employee_documents_list": "All of the above",
      "upload_or_generate": "Generate"
   }
}

Show details for a resource by its ID.

Update One by ID

PATCH /api/v1/orgs/:org_id/records/:rec_id

my_rec_id='5617'

curl "${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/records/${my_rec_id}" \
    -X 'PATCH' \
    -H "Authorization: Bearer ${OIDC_ACCESS_TOKEN}" \
    -H 'Content-Type: application/json' \
    --data-raw '{
      "fields": {
        "title": "Product Manager",
        "email": "john2@john.doe",
      }
    }' |
    jq
var myRecId = "5617";

var data = {
   fields: {
      title: "Product Manager",
      email: "john2@john.doe",
   },
};
var payload = JSON.stringify(data, null, 2);

var url = `${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/records/${myRecId}`;
var resp = await fetch(url, {
   method: "PATCH",
   headers: {
      Authorization: `Bearer ${OIDC_ACCESS_TOKEN}`,
      "Content-Type": "application/json",
   },
   body: payload,
});
var resInfo = await resp.json();

Example Response:

{
   "success": true,
   "type": "[]<string>",
   "total": 2,
   "changes": ["title", "email"],
   "count": 2
}

Update the properties of an existing resource

Workflows

The Workflow Library shows each of the available form collections for a particular task.

List All Workflow Templates

Get list of all available Workflow Templates.

GET /api/v1/orgs/:org_id/workflow-templates

curl "${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/workflow-templates" \
  -H "Authorization: Bearer ${OIDC_ACCESS_TOKEN}" |
  jq
var url = `${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/workflow-templates`;
var resp = await fetch(url, {
   headers: {
      Authorization: `Bearer ${OIDC_ACCESS_TOKEN}`,
   },
});
var library = await resp.json();

console.log(library);

Example Response: (simplified for readability)

{
   "success": true,
   "count": 71,
   "total": 71,
   "type": "[]<workflow_template>",
   "workflow_templates": [
      {
         "id": "flow_extg0vcffk3h9085",
         "label": "Entity Setup",
         "description": "Form a new entity, or upload your documentation for an existing entity.",
         "created_at": "2023-12-29T18:56:00.000Z",
         "updated_at": "2025-05-08T22:07:38.000Z"
      },
      {
         "id": "flow_extg0yj9vywzabwy",
         "label": "Fundraising Diligence Portal",
         "description": "Share pitch materials with prospective investors for initial screening. Set up custom Diligence Rooms to respond to requests & share relevant Data Room documentation.",
         "created_at": "2023-12-29T18:56:00.000Z",
         "updated_at": "2025-05-08T22:03:20.000Z"
      },
      {
         "id": "flow_extg0g5yjfrtfk3e",
         "label": "Onboard Employees",
         "description": "Onboard employees and generate appropriate documents. Or upload the documents that you've already executed.",
         "created_at": "2023-12-29T18:56:00.000Z",
         "updated_at": "2025-05-08T22:07:44.000Z"
      },
      {
         "id": "flow_extg0z7hs83avjbt",
         "label": "Annual Report (State Renewal)",
         "description": "Complete the annual report required by each state in which your company is registered.",
         "created_at": "2023-12-29T18:56:00.000Z",
         "updated_at": "2024-11-20T18:21:08.000Z"
      },
      {
         "id": "flow_extg0h07e96b2etx",
         "label": "Data Room Setup",
         "description": "Set up a custom data room that you can manage and share with relevant parties.",
         "created_at": "2023-12-29T18:56:00.000Z",
         "updated_at": "2023-12-02T00:33:47.000Z"
      },
      {
         "id": "flow_extg0v4ndq47rnwk",
         "label": "Doc Send & Sign",
         "description": "Send and Sign Custom Documents.",
         "created_at": "2023-12-29T18:56:00.000Z",
         "updated_at": "2024-10-31T00:05:52.000Z"
      }
      // ...rest of available templates
   ]
}

List All Started Workflows

Get List of started Workflows. Each Workflow was generated from a Workflow Template above.

GET /api/v1/orgs/:org_id/workflows

curl "${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/workflows" \
  -H "Authorization: Bearer ${OIDC_ACCESS_TOKEN}" |
  jq
var url = `${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/workflows`;
var resp = await fetch(url, {
   headers: {
      Authorization: `Bearer ${OIDC_ACCESS_TOKEN}`,
   },
});
var workflows = await resp.json();

console.log(workflows);

Example Response:

{
   "success": true,
   "total": 1,
   "count": 1,
   "workflows": [
      {
         "id": "work_q0yjdgdr77rdnedc",
         "label": "Entity Setup",
         "template_id": "flow_extg0vcffk3h9085",
         "status": "open",
         "created_at": "2025-10-22T19:41:06.000Z",
         "updated_at": "2025-10-22T19:50:07.000Z"
      }
   ]
}

Start a Workflow

POST /api/v1/orgs/:org_id/workflows

curl "${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/workflows" \
  -X 'POST' \
  -H "Authorization: Bearer ${OIDC_ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
   --data-raw '{
      "flow_id": "${my_flow_id}"
    }' |
  jq
var data = {
   flow_id: my_flow_id,
};
var payload = JSON.stringify(data, null, 2);

var url = `${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/workflows`;
var resp = await fetch(url, {
   method: "POST",
   headers: {
      Authorization: `Bearer ${OIDC_ACCESS_TOKEN}`,
      "Content-Type": "application/json",
   },
   body: payload,
});
var workflow = await resp.json();

console.log(workflow);

Example Response:

{
   "status": "success",
   "workflow_id": "wrk_01hcn8arzxb9heq3saaf97b7bx"
}

Opening a workflow will create the associated To-Do items in the PaperOS interface.

Start a Workflow with Prepopulated Data & Auto Submit

POST /api/v1/orgs/:org_id/workflows

curl "${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/workflows" \
  -X 'POST' \
  -H "Authorization: Bearer ${OIDC_ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
   --data-raw '{
      "flow_id": "${my_flow_id}",
      "records": [{
        "role": "company",
        "id": "rec_kp9fh3354dqt255m"
      }],
      "auto_submit": 1
    }' |
  jq
var data = {
   flow_id: my_flow_id,
   records: [
      {
         role: "company",
         id: "rec_kp9fh3354dqt255m",
      },
   ],
   auto_submit: 1,
};
var payload = JSON.stringify(data, null, 2);

var url = `${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/workflows`;
var resp = await fetch(url, {
   method: "POST",
   headers: {
      Authorization: `Bearer ${OIDC_ACCESS_TOKEN}`,
      "Content-Type": "application/json",
   },
   body: payload,
});
var workflow = await resp.json();

console.log(workflow);

Example Response:

{
   "status": "success",
   "workflow_id": "wrk_01hcn8arzxb9heq3saaf97b7bx"
}

Prepopulate a new workflow instance:

  1. See the Records section to create the resources that will be referenced here.
  2. List those resources when opening a workflow to make them available for autofill.

TODO: create slug for library item

Documents

A combination of documents waiting to be signed, completed, or uploaded. If document requires signatures, the signature links will be passed through recipients[].

Get Documents by Org

Get a list of documents for a specific account

GET /api/v1/orgs/{{org_id}}/documents GET /api/v1/org/documents?account_id={{account_id}}

curl "${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/documents" \
    -H "Authorization: Bearer ${OIDC_ACCESS_TOKEN}"
curl "$PAPEROS_BASE_URL/api/v1/org/documents?account_id=${account_id}" \
    -H "Authorization: Bearer ${OIDC_ACCESS_TOKEN}"
var url = `${paperBase}/api/v1/orgs/${my_org_id}/documents`;
var resp = await fetch(url, {
   method: "GET",
   headers: {
      Authorization: `Bearer ${OIDC_ACCESS_TOKEN}`,
   },
});
var url = `${paperBase}/api/v1/org/documents?account_id=${account_id}`;
var resp = await fetch(url, {
   method: "GET",
   headers: {
      Authorization: `Bearer ${OIDC_ACCESS_TOKEN}`,
   },
});
var resource = await resp.json();

console.log(resource);

Get Documents by Email

Get a list of documents for a specific account & email.

GET /api/v1/orgs/{{org_id}}/documents?email={{email}} GET /api/v1/org/documents?account_id={{account_id}}&email={{email}}

curl "${PAPEROS_BASE_URL}/api/v1/orgs/${my_org_id}/documents?email=${email}" \
    -H "Authorization: Bearer ${OIDC_ACCESS_TOKEN}"
curl "${PAPEROS_BASE_URL}/api/v1/org/documents?account_id=${account_id}&email=${email}" \
    -H "Authorization: Bearer ${OIDC_ACCESS_TOKEN}"
var url = `${paperBase}/api/v1/orgs/${my_org_id}/documents?email=${email}`;
var resp = await fetch(url, {
   method: "GET",
   headers: {
      Authorization: `Bearer ${OIDC_ACCESS_TOKEN}`,
   },
});
var url = `${paperBase}/api/v1/org/documents?account_id=${account_id}&email=${email}`;
var resp = await fetch(url, {
   method: "GET",
   headers: {
      Authorization: `Bearer ${OIDC_ACCESS_TOKEN}`,
   },
});
var resource = await resp.json();

console.log(resource);

Example Response:

{
   "success": true,
   "total": 3,
   "count": 3,
   "type": "[]<document>",
   "documents": [
      {
         "path": "/Archived Documentation/Historical Financials",
         "filename": "2017 - 2020 Balance Sheet.xlsx",
         "recipients": [],
         "url": "https://paperos.com/api/public/documents/820701358552/eyJ0eXAiOiJKV1QiLCJraWQiOiJKa3BxUW1faW9IeHRsb1BOTS12VE1IenkzR0xWLW1GbEhDdkxPMVJ0RlhVIiwiYWxnIjoiRVMyNTYifQ.eyJmaWxlIjoiODIwNzAxMzU4NTUyIiwiaWF0IjoxNjk3MjI3NTA0LCJleHAiOjE2OTcyMjg0MDQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMSJ9.iNPasWw1VfMDuNTTDHW16f5CgmXgKJUoyY9I6Ac2RPosGn61GSMDMgPXzi10uSk2Mg9SjtWclZxdIe5t6z-Lqw",
         "pub_id": "doc_0000000000p6a290bsjjqmkfk1"
      },
      {
         "path": "/Archived Documentation/Historical Financials",
         "filename": "2017 - 2020 PnL.xlsx",
         "recipients": [],
         "url": "https://paperos.com/api/public/documents/820688814651/eyJ0eXAiOiJKV1QiLCJraWQiOiJKa3BxUW1faW9IeHRsb1BOTS12VE1IenkzR0xWLW1GbEhDdkxPMVJ0RlhVIiwiYWxnIjoiRVMyNTYifQ.eyJmaWxlIjoiODIwNjg4ODE0NjUxIiwiaWF0IjoxNjk3MjI3NTA0LCJleHAiOjE2OTcyMjg0MDQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMSJ9.WMcM5HULwVyVmiDMMVtibStMou6C_O3Z2886VNZko7U7dBvOrqgPMcYaX0Ilt10q_iM_aj7LF29pNfTgxuXLxQ",
         "pub_id": "doc_00000000000b4j5gfbg8hb0sp6"
      },
      {
         "path": "/Archived Documentation/Historical Financials",
         "filename": "2017 - 2020 Statement of Cash Flows.xlsx",
         "recipients": [],
         "url": "https://paperos.com/api/public/documents/820700190254/eyJ0eXAiOiJKV1QiLCJraWQiOiJKa3BxUW1faW9IeHRsb1BOTS12VE1IenkzR0xWLW1GbEhDdkxPMVJ0RlhVIiwiYWxnIjoiRVMyNTYifQ.eyJmaWxlIjoiODIwNzAwMTkwMjU0IiwiaWF0IjoxNjk3MjI3NTA0LCJleHAiOjE2OTcyMjg0MDQsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMSJ9.HXaecHH5bq1QAw5TRQCqnb1dn8dE7spcQpLU1DM6J8AJYraTRqeOcLjRziA9P83YbFgMCzHKY-cDDiGsGoUVlw",
         "pub_id": "doc_0000000000y62e1skpa27jywhv"
      }
   ]
}

Errors

The PaperOS API uses the following error codes:

4xx

Error Code Meaning
400 Bad Request – Some of the query params or body props were missing or invalid
401 Unauthorized – The API key, user, or account is invalid or expired
402 Payment Required – The account must be upgraded to use this feature
403 Forbidden – The given key, user, or account doesn't have access
404 Not Found – The specified data was not found
405 Method Not Allowed – A GET was used where a POST was required, etc
406 Not Acceptable – The data was not in the proper format (i.e. JSON, CSV)
420 Enhance Your Calm – You're doing that too fast
422 Unprocessable Content – The format is correct, but the data is not
429 Too Many Requests – alias of 420

5xx

Error Code Meaning
500 Internal Server Error – We had a problem with our server. Please let us know
501 Not Implemented – The feature you tried to use is still in development
502 Bad Gateway – The application may be restarting