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

# Node.js

> Official Node.js wrapper for the Snipp API.

<Card horizontal icon="github" href="https://github.com/snipp-gg/snipp-js">
  snipp-gg/snipp-js
</Card>

<Card horizontal icon="npm" href="https://npmjs.com/package/@snipp-gg/snipp">
  @snipp-gg/snipp
</Card>

## Installation

```bash theme={null}
npm install @snipp-gg/snipp
```

Requires Node.js 18 or later. Zero dependencies. Uses native `fetch` and `FormData`.

## Getting Started

```javascript theme={null}
import { SnippClient } from "@snipp-gg/snipp";

const client = new SnippClient({ apiKey: process.env.SNIPP_API_KEY });

const { user } = await client.getUser("@me");
console.log(user.username);
```

### Regional endpoints

Pass `region` to pin the client to a specific regional host. Uploads run at the same speed either way; this controls which region stores your files.

```javascript theme={null}
const client = new SnippClient({
  apiKey: process.env.SNIPP_API_KEY,
  region: "eu-west-1", // or "us-west-1"
});
```

## Methods

Every method resolves to the parsed JSON response. Posts, files, and users follow the shapes in the [API reference](/api-reference/introduction), and the package ships JSDoc typedefs (`Post`, `PostFile`, `User`, `PostList`, and the rest) for editor completion.

### `getUser(id)`

Fetch a user by ID. Pass `"@me"` for the authenticated user. `api_key`, `key_has_uploads_access`, `upload_count`, and `limits` are only present on yourself.

```javascript theme={null}
const { user } = await client.getUser("@me");
console.log(user.plan, user.limits.usage.used_percent);
```

### `getUserPosts(id, options?)`

List a user's public posts, newest first. Team, private, unlisted, moderated, and restricted posts are never included. Pass `"@me"` for your own public posts.

| Option | Type | Description |
| - | - | - |
| `limit` | `number` | Posts per page (1 to 100, default 30). |
| `cursor` | `string` | `next_cursor` from the previous page. |

```javascript theme={null}
const { posts, has_more, next_cursor } = await client.getUserPosts("987654321098765432", { limit: 10 });
```

### `listPosts(options?)`

List your own posts of every privacy, newest first. Team posts are not included. Takes the same `limit` and `cursor` options as `getUserPosts`. Follow `next_cursor` until it is `null` to walk every page.

```javascript theme={null}
let cursor;
do {
  const page = await client.listPosts({ limit: 100, cursor });
  for (const post of page.posts) console.log(post.code, post.privacy);
  cursor = page.next_cursor ?? undefined;
} while (cursor);
```

### `getPost(code)`

Fetch a post by its share code. Team posts are only readable by members of that team, and have `like_count` and `liked` set to `null`.

```javascript theme={null}
const { post } = await client.getPost("AbCd1234");
console.log(post.url, post.files[0].url);
```

### `upload(file, options?)`

Upload a file as a new post. Accepts a `File`, `Blob`, `Buffer`, or `Uint8Array`. Resolves to `{ url, post }`, where `url` is the direct file URL and `post.url` is the share page.

| Option | Type | Description |
| - | - | - |
| `privacy` | `"public" \| "unlisted" \| "private"` | Visibility of the post. Defaults to `private` when omitted. |
| `filename` | `string` | Filename sent with the upload (defaults to `"upload"`). |
| `title` | `string` | Optional post title (max 30 chars). |
| `description` | `string` | Optional post description (max 200 chars). |
| `postType` | `"album" \| "individual"` | Sent as the `post-type` header. Has no effect through `upload()`, which sends a single file; use `addFiles` to build an album. |
| `includeMetadata` | `boolean` | Keep the file's metadata (EXIF, location, and the like). Omitted, the server strips it. |
| `priority` | `boolean` | Use priority (adaptive) streaming for videos. Omitted, the server enables it when your plan is eligible. |

```javascript theme={null}
import { readFileSync } from "node:fs";

const buffer = readFileSync("./image.png");
const { url, post } = await client.upload(buffer, { privacy: "unlisted", filename: "image.png" });
console.log(url, post.url);
```

### `updatePost(code, options)`

Update a post's title, description, or privacy. Only the fields you pass are changed. A team post's privacy cannot be changed. Resolves to `{ post }`.

| Option | Type | Description |
| - | - | - |
| `title` | `string` | New title (max 30 chars). Empty string to clear. |
| `description` | `string` | New description (max 200 chars). Empty string to clear. |
| `privacy` | `"public" \| "unlisted" \| "private"` | New visibility setting. |

```javascript theme={null}
await client.updatePost("AbCd1234", { title: "New title", privacy: "public" });
```

### `addFiles(code, files, options?)`

Add one or more files to an existing post, turning it into an album. The post's share code, privacy, title, and description are preserved. Posts cap at 50 files total. Requests that would exceed the cap are rejected. New files inherit the post's privacy. Resolves to `{ post, failed? }`; `failed` lists the files that were rejected, each with its `index` and `error`.

| Parameter | Type | Description |
| - | - | - |
| `code` | `string` | Share code of the post. |
| `files` | `Array<File \| Blob \| Buffer \| Uint8Array>` | One or more files to add. |
| `options.filenames` | `string[]` | Optional filenames (one per file). |
| `options.includeMetadata` | `boolean` | Keep the files' metadata (EXIF, location, and the like). Omitted, the server strips it. |
| `options.priority` | `boolean` | Use priority (adaptive) streaming for videos. Omitted, the server enables it when your plan is eligible. |

```javascript theme={null}
import { readFileSync } from "node:fs";

const a = readFileSync("./photo-a.png");
const b = readFileSync("./photo-b.png");

const { post, failed } = await client.addFiles("AbCd1234", [a, b], {
  filenames: ["photo-a.png", "photo-b.png"],
  includeMetadata: true,
});
console.log(post.file_count, failed ?? []);
```

### `deleteFile(code, name)`

Delete one file from a post, by the `name` it has in `post.files`. Deleting a post's only file deletes the post.

```javascript theme={null}
const { post } = await client.getPost("AbCd1234");
await client.deleteFile("AbCd1234", post.files[1].name);
```

### `deletePost(code)`

Delete a post and every file in it.

```javascript theme={null}
await client.deletePost("AbCd1234");
```

### `reportPost(code, reason?)`

Report a post, with an optional reason (max 200 chars).

```javascript theme={null}
await client.reportPost("AbCd1234", "Spam");
```

### `reportUser(id, reason?)`

Report a user, with an optional reason (max 200 chars).

```javascript theme={null}
await client.reportUser("987654321098765432", "Impersonation");
```

## Error Handling

All API errors throw a `SnippError` with these properties:

| Property | Type | Description |
| - | - | - |
| `status` | `number` | HTTP status code. |
| `type` | `string \| null` | Error type, such as `not_found`, `rate_limited`, or `quota_exceeded`. `null` when the response did not carry one. |
| `message` | `string` | Human-readable message, or the HTTP status text when the response did not carry one. |
| `body` | `object \| null` | Parsed JSON response, or `null` when it was not JSON. Context fields live under `body.error`, such as `resets_at` on `quota_exceeded`. |

```javascript theme={null}
import { SnippClient, SnippError } from "@snipp-gg/snipp";

try {
  await client.upload(buffer, { filename: "image.png" });
} catch (err) {
  if (err instanceof SnippError && err.type === "quota_exceeded") {
    console.error(`Weekly quota used up, resets at ${err.body.error.resets_at}`);
  } else {
    throw err;
  }
}
```

## Migrating from 2.x

3.0 follows the reorganized Snipp API.

| 2.x | 3.0 |
| - | - |
| `listUploads({ limit })` | `listPosts({ limit, cursor })`, cursor-paginated posts |
| `editUpload(code, options)` | `updatePost(code, options)` |
| `appendUpload(code, files, options)` | `addFiles(code, files, options)` |
| `deleteUpload(filename)` | `deleteFile(code, name)`, which now needs the post's share code |
| `getUser(id, { includePosts, postsLimit })` | `getUser(id)` plus `getUserPosts(id, { limit, cursor })` |
| | New: `deletePost(code)`, `reportUser(id, reason?)` |

* Posts use one shape everywhere: `privacy` (was `post_privacy`), `created_at` (was `created`), `view_count` (was `views`), `files[].name` (was `file_name`), and `file_count` replaces `is_album`. The top-level `urls`, `file`, and `thumbnail_url` are gone; read `files`.
* `upload()` resolves to `{ url, post }`; `message`, `file`, and `processing_time` are gone.
* Users carry `plan` (`free`, `plus`, or `ultra`) in place of `plus`, `ultra`, and `badges`, `created_at` in place of `created`, and `blocking` in place of `blocked_by_you`.
* The API now sends every error as an `error` object with `type`, `message`, and any context fields. `SnippError` gains `type`, and context fields moved from the top of `body` into `body.error`. A check like `err.body?.suspended` becomes `err.type === "suspended"`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.