> ## Documentation Index
> Fetch the complete documentation index at: https://developers.gonitro.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Best Practices

> Guidelines for authentication, error handling, document limits, pagination, and security when using the NSEV WebPortal API.

## Authentication

**Cache access tokens.** OAuth2 tokens are valid for the duration specified in `expires_in`
(typically 300 seconds). Cache the token and reuse it across requests. Requesting a new token on
every API call is unnecessary and may be rate-limited by your IDP.

**Use the refresh token.** When a token expires, use the `refresh_token` to obtain a new access
token without re-authenticating with your full credentials.

**Include a User-Agent.** Every request must include a `User-Agent` header with a non-empty value.
The default user agent for `curl` or `wget` may be blocked by the Web Application Firewall:

```bash theme={null}
-H "User-Agent: MyApp/1.0"
```

***

## Request format

**Always use valid JSON.** The Web Application Firewall validates all JSON before it reaches the
API. It will reject requests containing:

* Inline comments (`//` or `/* */`)
* Trailing commas
* Any other malformed JSON

**Omit optional fields you are not using.** Do not include optional fields with empty strings or
null values. An empty string is treated as a value and may cause a validation error:

```json theme={null}
// Correct - omit fields you don't need
{ "Name": "My Package", "Initiator": "user@example.com" }

// Incorrect - empty string for an optional field may cause errors
{ "Name": "My Package", "Initiator": "user@example.com", "ExternalReference": "" }
```

***

## Document limits

Keep document sizes within these limits to ensure reliable processing:

| Limit                         | Value        |
| ----------------------------- | ------------ |
| Maximum document size         | 30 MB        |
| Maximum package size          | 150 MB total |
| Maximum documents per package | 15           |

Large files can affect signing performance depending on the signer's internet connection. Compress
PDFs where possible before uploading.

All uploaded documents must be valid, conforming files. The API performs best-effort conversions,
but uploading non-standard documents produces undefined behavior.

***

## Error handling

**Trust the HTTP status code first.** The status code tells you whether a request succeeded or
failed:

| Status                      | Meaning                                                    |
| --------------------------- | ---------------------------------------------------------- |
| `200 OK`                    | Request succeeded, response body contains the resource     |
| `201 Created`               | Resource was created, location is in the `Location` header |
| `204 No Content`            | Request succeeded, no response body                        |
| `400 Bad Request`           | Client error - check request body and parameters           |
| `401 Unauthorized`          | Missing or invalid authentication credentials              |
| `404 Not Found`             | Resource does not exist                                    |
| `409 Conflict`              | Operation cannot be performed in the current state         |
| `500 Internal Server Error` | Server-side error - do not retry automatically             |

**Read the error body for details.** When a `4xx` response is returned, the body is an array of
structured error objects:

```json theme={null}
[
  {
    "ErrorCode": "Package.NotFound:3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "ErrorMessage": "Package with id [3fa85f64-5717-4562-b3fc-2c963f66afa6] was not found"
  }
]
```

Error codes follow the format `Resource.Error:parameter`. Use the `ErrorCode` value in your logic -
the `ErrorMessage` text is for diagnostic purposes only and may change between versions.

**Retry strategy.** `5xx` errors may be transient. Apply exponential backoff for retries. Do not
retry `4xx` errors automatically - they indicate a problem with the request itself that must be
fixed first.

***

## Pagination

Some list endpoints return paginated results. Two pagination styles are used:

**Page-based** (Packages, Actors):

```bash theme={null}
GET /packages?pageNumber=0&pageSize=20
```

Use `pageNumber` (zero-indexed) and `pageSize` to navigate through results. Increment `pageNumber`
until you have retrieved all items or `Items` in the response is empty.

**Token-based** (Configuration endpoints):

```bash theme={null}
GET /themes?maxQuantity=20&continuationToken={token}
```

The response includes a `continuationToken`. Pass it in the next request to retrieve the following
page. Stop when no `continuationToken` is returned.

***

## Package lifecycle

Steps must be performed in order. The following operations are only valid on packages in `Draft`
status:

1. Upload documents
2. Add stakeholders
3. Create actors and place signing elements
4. Set status to `pending` to activate

Attempting to modify documents or stakeholders on an active (`Pending` or later) package will
result in a `409 Conflict` error.

***

## Choosing the initiator

Every package has an `Initiator`: the email address of the user who sends it. The initiator must
be a known user in the WebPortal - an unknown address is rejected with `Package.InitiatorInvalid`.
By default the package lands in that user's personal **My Documents** folder, and the initiator is
the account the package is attributed to in the WebPortal.

Who you choose as the initiator shapes how the package behaves day to day:

**A real user.** The package appears in that person's WebPortal account, and they are treated as
its owner. Consider this when a specific employee genuinely owns the signing request and is
expected to manage it from the WebPortal.

**A technical (service) user.** A dedicated, non-personal account exists only to drive the API.
Consider this for fully API-driven flows: a service account does not leave when an employee
does, so packages are not tied to a personal mailbox that may later be deactivated. For a
hands-off integration, pair the service-user initiator with the controls below so the package is
driven entirely by your system rather than by WebPortal email:

* Set `SuppressNotifications` on each actor to stop NSEV emailing them directly (see below).
* Set a `CallBackUrl` so your system is told about status changes instead of relying on
  WebPortal notifications - see [Callbacks and redirects](/docs/nsev/build/guides/callbacks).

<Note>
  `SuppressNotifications` is a per-actor setting; its default is `false`, meaning notifications are
  sent. Setting it to `true` suppresses the package notifications NSEV would send that actor - but
  the actor still receives automatic reminders and expiration reminders if those are enabled on the
  package.
</Note>

<Tip>
  To have the initiator also receive the finished package, set `AddInitiatorAsReceiver` to `true`
  on the create-package call instead of adding the initiator as a receiver actor by hand.
</Tip>

***

## Use ExternalReference

Most objects you create - packages, stakeholders, elements - accept an optional
`ExternalReference`: a free-text value (maximum length 256) that NSEV stores but never uses
itself. Use it to carry your own system's identifier on the NSEV object, so you can correlate a
package, signer, or field back to the record it came from without maintaining a separate mapping
table.

`ExternalReference` also surfaces in redirect flows. When an actor finishes and is redirected back
to your application, NSEV appends the stakeholder's value as `ExternalReference` and the package's
value as `PackageExternalReference` to the redirect URL's query parameters. Setting meaningful
references therefore lets your return endpoint identify which package and signer just completed -
see [Callbacks and redirects](/docs/nsev/build/guides/callbacks) for the redirect
parameters and [The package model](/docs/nsev/build/concepts/package-model#externalreference)
for which objects carry a reference.

<Tip>
  Use a stable, unique value from your own system (for example a contract or case ID). Because the
  field is opaque to NSEV, consistency is entirely up to you - adopt one convention across packages,
  stakeholders, and elements so correlation stays predictable.
</Tip>

***

## Security

* Store credentials in environment variables or a secrets manager - never in source code or version control
* Use HTTPS for all API calls - plain HTTP is not acceptable
* Request signer action URLs just before redirecting users to them. URLs have a limited lifetime (default: 1 day) and may be single-use depending on your instance configuration
* Rotate credentials if they are accidentally exposed
