> For the complete documentation index, see [llms.txt](https://docs.pinecards.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.pinecards.app/backend/server-library.md).

# Server Library

Process workspace data by using Pine's API client.

{% hint style="info" %}
This library is only intended for use on the backend. Use the [`@pinecards/client` library ](/frontend/client-library.md)to communicate with Pine on the frontend.
{% endhint %}

The `@pinecards/server` library provides an easy way to interact with the Pine API in a type-safe manner. To get started, you'll need to install the library in your  project with your preferred package manager:

```bash
npm install @pinecards/server
```

You can then import the `PineClient` class and construct it with your authorization token:

```typescript
import { PineClient } from "@pinecards/server";

const client = new PineClient({ accessToken: "YOUR_TOKEN" });
```

The `PineClient` uses [tRPC](https://trpc.io/) under the hood to make network requests. As a result, this requires explicitly denoting whether a certain operation is a **query** or a **mutation**.

```typescript
client.cards.list.query({ where: { limit: 500 } });
```

{% hint style="info" %}
The `list` and `read` operations are **queries**, while the `create`, `update`, and `delete` operations are **mutations**.

Following this convention allows the client to use other [React](https://react.dev/) libraries that interface with [tRPC](https://trpc.io/).
{% endhint %}

## Queries

*Read* queries are the simplest operations as they only require a valid `id`as part of their `where` argument:

```typescript
const deck = await client.decks.read.query({ where: { id: "..." } });
const card = await client.cards.read.query({ where: { id: "..." } });
```

*List* queries rely on cursor-based pagination and can thus optionally take a `limit` (min 1, max 500) and a `cursor` argument:

```typescript
const response = await client.decks.list.query({ where: { limit: 500 } });

if (response.cursor) {
  const { data } = await client.decks.list.query({
    where: { cursor: response.cursor }
  });
}

```

## Mutations

*Create* mutations take `data` inputs that depend on the model that is being operated on:

* **Decks** require a `title` argument that accepts an input array that conforms to Pine's inline text editor.
* **Cards** require a `title` and `body` argument that conforms to Pine's block text editor.
* **Associations** (comments, etc..)  require a `body` argument that conforms to Pine's block text editor and a `where` argument for specifying the parent model to which the association should be added.
* **Connections** (links, backlinks, etc..)  require a `body` argument that conforms to Pine's block text editor and a `where` argument for specifying the parent model to which the connection should be added.

```typescript
// create a deck with bolded inline text
const deck = await client.decks.create.mutate({
  data: {
    title: [
      {
        type: "text",
        text: { text: "Example text" },
        marks: [{ type: "bold" }]
      }
    ]
  }
});

// create a card with block elements that have inline text
const card = await client.cards.create.mutate({
  data: {
    title: [
      {
        type: "heading",
        heading: { color: "gray" },
        content: [{ type: "text", text: { text: "Question" } }]
      }
    ],
    body: [
      {
        type: "paragraph",
        paragraph: { color: "gray" },
        content: [{ type: "text", text: { text: "Answer" } }]
      }
    ]
  }
});

// create an association with a paragraph that indents another paragraph
const association = await client.cards.associations.create.mutate({
  where: { parent: { id: card.id } },
  data: {
    body: [
      {
        type: "paragraph",
        paragraph: { color: "gray" },
        children: [{ type: "paragraph", paragraph: { color: "gray" } }]
      }
    ]
  }
});

// create a connection with a paragraph that indents another paragraph
const connection = await client.cards.connections.create.mutate({
  where: { parent: { id: card.id } },
  data: {
    body: [
      {
        type: "paragraph",
        paragraph: { color: "gray" },
        children: [{ type: "paragraph", paragraph: { color: "gray" } }]
      }
    ]
  }
});
```

{% hint style="info" %}
Pine's editor follows a hierarchical schema (similar to Notion!), where the structure of the data maps neatly to what gets rendered on the screen.
{% endhint %}

*Update* mutations are similar, except they require a `where` argument and optional `data`:

```typescript
// update the target deck
const deck = await client.decks.update.mutate({
  where: { id: "..." },
  data: {}
});

// update the target card
const card = await client.cards.update.mutate({
  where: { id: "..." },
  data: {}
});

// update the target association
const association = await client.cards.associations.update.mutate({
  where: { id: "...", parent: { id: "..." } },
  data: {}
});

// update the target connection
const connection = await client.cards.connections.update.mutate({
  where: { id: "...", parent: { id: "..." } },
  data: {}
});

```

*Delete* mutations only require a `where` argument:

```typescript
await client.decks.delete.mutate({
  where: { id: "..." }
});

await client.cards.delete.mutate({
  where: { id: "..." }
});

await client.cards.associations.delete.mutate({
  where: { id: "...", parent: { id: "..." } }
});

await client.cards.connections.delete.mutate({
  where: { id: "...", parent: { id: "..." } }
});
```

## Fields

The Fields API allows you to query a workspace's configured fields. This will return an object/dictionary data structure with the appropriate field type matching the corresponding value type:

| Field type | Value type |
| ---------- | ---------- |
| `text`     | `string`   |
| `number`   | `number`   |
| `switch`   | `boolean`  |
| `date`     | `string`   |

Fields that haven't been assigned a value will return a `null` value.&#x20;

You can retrieve the value of a configured number field as follows:

```typescript
const fields = await client.fields.list({ where: {} });
const value = fields.data.find(field => field.id === "ID_FROM_UI")
```

## Webhooks

Pine provides webhooks for `Deck` and `Card` events, allowing you to listen to any workspace changes that affect these models.

Webhook events are sent to a publicly accessible HTTPS URL via a `HTTP POST`request. The `POST` request expects a `HTTP 200` status code in response and will be retried only once if it fails to receive it.

{% hint style="info" %}
Integrations are discouraged from polling the API to fetch data updates to avoid hitting rate limits.
{% endhint %}

The `@pinecards/server` library exports a `PineWebhooks` class for securely constructing a webhook payload. Here's a simple demonstration using the `express` library:

```javascript
import express from "express";
import bodyParser from "body-parser";
import { PineWebhooks } from "@pinecards/server";

const app = express();
const webhooks = new PineWebhooks({ secret: "SIGNING_SECRET" });

app.use(
  bodyParser.json({
    verify: (req, res, buf) => {
      req.rawBody = buf;
    }
  })
);

app.post("/", (req, res) => {
  const event = webhooks.construct(req.rawBody, req.headers["pine-signature"]);

  // ... do something with data ...

  res.sendStatus(200);
});

app.listen(3001, () => {
  console.log(`Webhook server is running at http://localhost:3001`);
});

```

Under the hood, Pine verifies that the signature contained in `req.headers["pine-signature"]` matches the signature that is constructed from the raw body:

```typescript
const constructedSignature = crypto
   .createHmac("sha256", inputSecret)
   .update(rawBody)
   .digest("hex");

 if (constructedSignature !== inputSignature) {
   throw new TRPCClientError("Invalid webhook signature");
 }
```

The event that is returned from the `construct` function contains the following fields:

| Field              | Description                                                                  |
| ------------------ | ---------------------------------------------------------------------------- |
| `id`               | The unique identifier for the webhook event.                                 |
| `action`           | The `create`, `update`, or `delete` action.                                  |
| `data`             | The `Deck` or `Card` data that changed.                                      |
| `webhookTimestamp` | Timestamp of when the webhook was sent to help guard against replay attacks. |
