# collections

Source: https://docs.korala.ai/guides/collections

---

# Collections

Use collections to group documents and templates by client, project, or matter.
You can leave a resource unassigned or add it to several collections. You keep
the same resource when you add or remove a membership.

Collections belong to your organization and are separate in live and test mode.
API requests use the key’s mode. Dashboard requests can select
`sandbox=true`; omitting it selects live mode. Collections grant no additional
access and are not visible to signers.

## Find collections in the dashboard

Open **Documents → Collections** to organize related documents and templates.
Select prepared documents and choose **Send for signing** to prepare a request.
Use **Documents → Signing requests** to track individual and grouped requests.
A collection shows its documents, templates, and related signing requests. Use its actions menu
to edit, archive, or restore it.

The **All collections** control above the Documents and Templates tables filters
by collection or shows unassigned work. Select **All collections** inside the
control to clear the filter.

On a document or template, open **Details** and use **Edit** in the Collections
panel to change its memberships. Removing a membership keeps the resource.
During creation, choose collections alongside the name after selecting a file.

## Create or retrieve a collection

Use an external reference to reconcile a retried creation request. Choose a case-sensitive external ID and preserve its whitespace. You cannot
change it after creation or reuse it within the same organization and mode,
even after archiving the collection. A duplicate creation returns `409 Conflict`; retrieve and inspect
the existing record rather than overwriting it.

```typescript

const client = new KoralaClient({
  apiKeyId: process.env.KORALA_API_KEY_ID!,
  apiSecret: process.env.KORALA_API_SECRET!,
});

const externalId = 'crm:client:acme';
const collection = await client.collections.create({
  name: 'Acme onboarding',
  externalId,
  metadata: { project: 'onboarding' },
}).catch(async error => {
  if (error instanceof KoralaError && error.status === 409) {
    return client.collections.byExternalId(externalId);
  }
  throw error;
});
```

The equivalent API operations are `POST /api/v1/collections` and
`GET /api/v1/collections/by-external-id?externalId=crm%3Aclient%3Aacme`.
Korala trims surrounding whitespace from names and requires 1–255 characters. Descriptions allow up to
2,000 characters; external IDs allow 1–255. Metadata must be a JSON object of
at most 16 KiB when encoded as UTF-8 JSON. Updating metadata replaces the object.

## Put new work in a collection

Supply `collectionIds` when requesting a document or template upload URL, or
when creating a document from a template. Resource creation and assignment
commit together. An invalid or archived destination fails creation.

```typescript
const upload = await client.documents.createUploadUrl({
  filename: 'agreement.pdf',
  contentType: 'application/pdf',
  collectionIds: [collection.id],
});
// PUT the PDF bytes to upload.uploadUrl, then confirm the upload.
await client.documents.confirmUpload(upload.documentId);

const document = await client.templates.createDocument(templateId, {
  name: 'Acme offer',
  signers: { Employee: { name: 'Alex', email: 'alex@example.com' } },
  collectionIds: [collection.id],
});
```

Choose destinations for each generated document; **template memberships do not
carry over**. Omitting `collectionIds` or supplying
`[]` creates an unassigned resource. A reused template can have a different mode
from the generated document; destinations must match the document's mode.

In the dashboard, creation flows started from a collection preselect it. Clear
or change the selection before submitting to choose another destination.

## Organize and find existing resources

```typescript
await client.collections.addDocuments(collection.id, [document.id]);
await client.collections.addTemplates(collection.id, [templateId]);

const page = await client.documents.listPage({
  collectionId: collection.id,
  status: DocumentStatus.Pending,
  search: 'offer',
  includeCollections: true,
  page: 1,
  limit: 20,
});

const unassigned = await client.templates.list({ unassigned: true });
await client.collections.removeDocuments(collection.id, [document.id]);
```

Membership operations accept 1–100 distinct resource IDs. Repeated add/remove
requests have the same result; an invalid resource rejects the whole request.
Creation accepts up to 100 distinct destination collection IDs. These are
request limits, not lifetime membership limits.

Collection lists default to active records. Use
`archiveStatus=active|archived|all`, `search`, `page`, and `limit`. Collection and
member endpoints default to 20 rows and cap pages at 100. Search is a literal,
case-insensitive substring match; filters run before counting and pagination.
Document and template lists support `collectionId`, `collectionIds`, or
`unassigned=true`; these filters cannot be combined. `collectionIds` accepts
1–100 distinct UUIDs and matches membership in **any** selected collection.
Each resource appears once, including in pagination totals. All collection IDs
must belong to the authenticated organization and mode; an invalid or
inaccessible collection rejects the request. Archived memberships are included.

```typescript
const page = await client.documents.listPage({
  collectionIds: [clientCollection.id, projectCollection.id],
  search: 'offer',
  status: DocumentStatus.Pending,
});
```

HTTP clients can send `?collectionIds=UUID1,UUID2` or repeated
`collectionIds=UUID1&collectionIds=UUID2` parameters. Search, status, and other
filters still apply to the resulting documents. The SDK serializes arrays for you.

Use `client.documents.listPage({ collectionIds: [...] })` or
`client.templates.listPage({ collectionIds: [...] })` to search several collections.
`client.collections.listDocuments(id, query)` and `listTemplates(id, query)`
search the one collection named by `id`; their query accepts resource filters
such as search and status/activity, but no collection assignment filters.

Authenticated detail responses include `collectionIds`. List responses include
it when `includeCollections=true`; an empty array means unassigned. Member lists
use `/collections/:id/documents` and `/collections/:id/templates`.

## Create a signing packet

Prepare and send individual documents for signing before selecting them for a
packet. Collection templates must first generate documents with signer records.

```typescript
const eligible = await client.signingPackets.listEligibleDocuments({
  collectionId: collection.id,
  recipientEmail: 'alex@example.com',
});
// Review the documents and resolve the exact signer record for each one.
const reviewedItems = eligible.data.map(document => ({
  documentId: document.id,
  signerId: document.signers[0].id,
}));
const packet = await client.signingPackets.create({
  name: 'Acme onboarding signatures',
  sourceCollectionId: collection.id,
  recipient: { name: 'Alex', email: 'alex@example.com' },
  items: reviewedItems,
});
const history = await client.signingPackets.list({
  sourceCollectionId: collection.id,
});
```

Review the final selection before creating the packet. Existing packet rules
apply, including mode, recipient, eligibility, and the 50-document limit. The
server revalidates membership at creation. Changes require another review;
the server does not silently remove selected documents. Send the packet through
the [existing packet workflow](https://docs.korala.ai/guides/signing-packets).

Use `sourceCollectionId` to trace a packet to its collection. Later membership,
archive, or name changes leave the packet's selected items unchanged. Source
revisions still freeze at send. Only authenticated management responses expose
the origin link.

## Archive and restore

```typescript
await client.collections.archive(collection.id);
await client.collections.restore(collection.id);
```

Archiving preserves members and packet history, and existing signing continues.
You can read and edit archived collections and remove their members. Restore before adding members or creating a new packet. Permanent
collection deletion is not available.

A template's mode cannot change while it has any collection memberships,
including archived ones. Remove memberships first or duplicate the template
into the other mode.
