{"id":41419181,"url":"https://github.com/baileyherbert/envato.js","last_synced_at":"2026-01-23T14:00:15.562Z","repository":{"id":23942931,"uuid":"99971089","full_name":"baileyherbert/envato.js","owner":"baileyherbert","description":"🍂 Library for working with the Envato API.","archived":false,"fork":false,"pushed_at":"2024-06-17T09:54:53.000Z","size":371,"stargazers_count":22,"open_issues_count":1,"forks_count":12,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-10-30T21:43:46.578Z","etag":null,"topics":["codecanyon","envato","envato-api","envato-market","oauth","personal-token","themeforest"],"latest_commit_sha":null,"homepage":"https://npmjs.com/envato","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/baileyherbert.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-08-10T22:56:17.000Z","updated_at":"2025-10-26T22:22:32.000Z","dependencies_parsed_at":"2024-10-06T13:00:35.580Z","dependency_job_id":null,"html_url":"https://github.com/baileyherbert/envato.js","commit_stats":{"total_commits":81,"total_committers":5,"mean_commits":16.2,"dds":"0.41975308641975306","last_synced_commit":"d0abb0cce63d75c0f2e88648c37de42e7197272a"},"previous_names":["baileyherbert/node-envato-api"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/baileyherbert/envato.js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baileyherbert%2Fenvato.js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baileyherbert%2Fenvato.js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baileyherbert%2Fenvato.js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baileyherbert%2Fenvato.js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/baileyherbert","download_url":"https://codeload.github.com/baileyherbert/envato.js/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/baileyherbert%2Fenvato.js/sbom","scorecard":{"id":223949,"data":{"date":"2025-08-11","repo":{"name":"github.com/baileyherbert/envato.js","commit":"419ea73956be58f6bffcfb7649fa3eae5af8ba65"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/28 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/publish.yml:1","Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":5,"reason":"dependency not pinned by hash detected -- score normalized to 5","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/baileyherbert/envato.js/publish.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/baileyherbert/envato.js/publish.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/publish.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/baileyherbert/envato.js/publish.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/baileyherbert/envato.js/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/baileyherbert/envato.js/test.yml/master?enable=pin","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   2 out of   2 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 2 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":5,"reason":"5 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55","Warn: Project is vulnerable to: GHSA-76p7-773f-r4q5"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-17T03:13:25.605Z","repository_id":23942931,"created_at":"2025-08-17T03:13:25.605Z","updated_at":"2025-08-17T03:13:25.605Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28693435,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-23T11:01:27.039Z","status":"ssl_error","status_checked_at":"2026-01-23T11:00:26.909Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codecanyon","envato","envato-api","envato-market","oauth","personal-token","themeforest"],"created_at":"2026-01-23T14:00:08.202Z","updated_at":"2026-01-23T14:00:15.536Z","avatar_url":"https://github.com/baileyherbert.png","language":"TypeScript","readme":"[![Envato.js](https://i.bailey.sh/pAEYtWD.png)](https://github.com/baileyherbert/envato.js)\n\n\u003cp align=\"center\"\u003e\n  \u003ca aria-label=\"Build\" href=\"https://github.com/baileyherbert/envato.js/actions/workflows/test.yml\"\u003e\n    \u003cimg alt=\"\" src=\"https://img.shields.io/github/actions/workflow/status/baileyherbert/envato.js/test.yml?style=for-the-badge\u0026labelColor=000000\"\u003e\n  \u003c/a\u003e\n  \u003ca aria-label=\"NPM version\" href=\"https://www.npmjs.com/package/envato\"\u003e\n    \u003cimg alt=\"\" src=\"https://img.shields.io/npm/v/envato.svg?style=for-the-badge\u0026labelColor=000000\"\u003e\n  \u003c/a\u003e\n  \u003ca aria-label=\"License\" href=\"https://github.com/baileyherbert/envato.js/blob/master/LICENSE\"\u003e\n    \u003cimg alt=\"\" src=\"https://img.shields.io/npm/l/envato.svg?style=for-the-badge\u0026labelColor=000000\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n---\n\nThis is a library for working with the new Envato API.\n\n- Methods available for all endpoints at [build.envato.com](https://build.envato.com/).\n- Supports both OAuth and personal tokens.\n- Handles rate limits and concurrency for you.\n- Full type hinting and autocompletion in modern editors.\n\nEnvato.js extracts the relevant data from responses and returns them without any extra bloat. It also converts data\nwhere possible, such as converting timestamps into `Date` objects.\n\n---\n\n- [Basic usage](#basic-usage)\n- [Creating a client](#creating-a-client)\n  - [Personal token](#personal-token)\n  - [OAuth](#oauth)\n  - [Client options](#client-options)\n    - [User agent](#user-agent)\n    - [Request options](#request-options)\n    - [Rate limiting](#rate-limiting)\n    - [Concurrency](#concurrency)\n- [Sending requests](#sending-requests)\n  - [Using promises](#using-promises)\n  - [Getting a user's identity](#getting-a-users-identity)\n  - [Manually sending requests](#manually-sending-requests)\n  - [Catalog](#catalog)\n    - [Look up a public collection](#look-up-a-public-collection)\n    - [Look up a single item](#look-up-a-single-item)\n    - [Look up a WordPress theme or plugin's version](#look-up-a-wordpress-theme-or-plugins-version)\n    - [Search for items](#search-for-items)\n    - [Search for comments](#search-for-comments)\n    - [Get more like this](#get-more-like-this)\n    - [Get popular items by site](#get-popular-items-by-site)\n    - [Get categories by site](#get-categories-by-site)\n    - [Get prices for an item](#get-prices-for-an-item)\n    - [Get new items by site and category](#get-new-items-by-site-and-category)\n    - [Find featured items and authors](#find-featured-items-and-authors)\n    - [Get random new items](#get-random-new-items)\n  - [User](#user)\n    - [List a user's collections](#list-a-users-collections)\n    - [Look up a user's private collection](#look-up-a-users-private-collection)\n    - [Get a user's profile details](#get-a-users-profile-details)\n    - [List a user's badges](#list-a-users-badges)\n    - [Get a user's item count per marketplace](#get-a-users-item-count-per-marketplace)\n    - [Get a user's newest items](#get-a-users-newest-items)\n  - [Private user](#private-user)\n    - [List an author's sales](#list-an-authors-sales)\n    - [Look up a sale by code](#look-up-a-sale-by-code)\n    - [List all purchases](#list-all-purchases)\n    - [List a buyer's purchases](#list-a-buyers-purchases)\n    - [Look up a purchase by code](#look-up-a-purchase-by-code)\n    - [Get a user's account details](#get-a-users-account-details)\n    - [Get a user's username](#get-a-users-username)\n    - [Get a user's email](#get-a-users-email)\n    - [Get an author's monthly sales](#get-an-authors-monthly-sales)\n    - [Get statement data](#get-statement-data)\n    - [Download a purchase](#download-a-purchase)\n  - [Statistics](#statistics)\n    - [Get the total number of market users](#get-the-total-number-of-market-users)\n    - [Get the total number of market items](#get-the-total-number-of-market-items)\n    - [Get the number of items per category](#get-the-number-of-items-per-category)\n- [Error handling](#error-handling)\n  - [Error codes](#error-codes)\n  - [Not found errors](#not-found-errors)\n- [Events](#events)\n  - [Renew](#renew)\n  - [Debug](#debug)\n  - [Ratelimit](#ratelimit)\n  - [Resume](#resume)\n- [Sandboxing](#sandboxing)\n\n---\n\n## Basic usage\n\nTo get started, install [the package](https://npmjs.com/package/envato) into your project and require it. Then you can begin using the exported `Client` and `OAuth` classes.\n\n```\nnpm install envato\n```\n\n```js\nconst Envato = require('envato');\n\nasync function start() {\n    const client = new Envato.Client('personal token here');\n\n    const { userId } = await client.getIdentity();\n    const username = await client.private.getUsername();\n    const email = await client.private.getEmail();\n\n    console.log('Logged in:', userId, username, email);\n}\n\nstart().catch(console.error);\n```\n\n## Creating a client\n\nBefore you can send requests, you must create a client instance with your personal token or an OAuth access token.\n\n### Personal token\n\nYou can create a client from a personal token by passing the `token` parameter into the client options. You can generate a new token at https://build.envato.com/create-token.\n\n```js\nconst client = new Envato.Client('your personal token');\nconst client = new Envato.Client({\n    token: 'your personal token'\n});\n```\n\n### OAuth\n\nThe first step to working with OAuth is creating the OAuth helper instance. Supply the client ID, client secret, and redirect URI exactly as they were configured via [build.envato.com](https://build.envato.com/register):\n\n```js\nconst oauth = new Envato.OAuth({\n    client_id: '',\n    client_secret: '',\n    redirect_uri: ''\n});\n```\n\nNext, redirect the user to the authentication screen using the `getRedirectUrl` method:\n\n```js\nres.redirect(oauth.getRedirectUrl());\n```\n\nOnce the user returns from a successful authentication, the current URL should contain a `code` query parameter with an authentication code. Pass this code to `getClient` to finish the authentication process.\n\n```js\n// This is an example express route to handle the request\napp.get('/authenticate', async function(req, res) {\n    try {\n        // Fetch the code from the URL\n        const code = req.query.code;\n\n        // Authenticate the user via the API\n    \tconst client = await oauth.getClient(code);\n        const username = await client.private.getUsername();\n\n        // You can also get their User ID for data storage\n        const { userId } = await client.getIdentity();\n\n        res.send(`Hello, ${username}!`);\n    }\n    catch (error) {\n        console.error('Authentication failed:', error);\n        res.status(500).send('Something broke! Try again.');\n    }\n});\n```\n\nOnce you have your `client` instance, you can extract the access and refresh tokens, as well as the expiration time, and store them in a database for future use.\n\n```js\nconst { token, refreshToken, expiration } = client;\n\nconsole.log('The current access token is %s', token);\nconsole.log('It expires at %d', expiration);\n```\n\nIn the future, if you need to reconstruct a client instance from these parameters, you must pass all three (as well as the OAuth helper instance) to enable automatic renewal of the access token.\n\n```js\n// For automatic renewal of access tokens, you'll need an OAuth instance\n// It must have the same credentials as the instance that originally authenticated the user\nconst oauth = new Envato.OAuth(...);\n\n// To construct a client with automatic renewal, pass all properties and an OAuth instance\n// Once the token expires, it will be automatically renewed\nconst client = new Envato.Client({\n    token: fakeStorage.get('accessToken'),\n    refreshToken: fakeStorage.get('refreshToken'),\n    expiration: fakeStorage.get('expiration'),\n    oauth\n});\n\n// To construct a client WITHOUT automatic renewal, simply pass the token\n// Once the token expires, the client will stop working\nconst client = new Envato.Client(fakeStorage.get('token'));\n```\n\nAdditionally, you will likely want to keep track of the new access token and expiration times whenever the client regenerates it. To do this, listen to the `renew` event on the client instance.\n\n```js\nclient.on('renew', renewal =\u003e {\n    fakeStorage.set('accessToken', renewal.accessToken);\n    fakeStorage.set('expiration', renewal.expiration);\n});\n```\n\n### Client options\n\n#### User agent\n\nFor high-volume users, it is highly recommended that you provide a user agent with your requests. This agent should briefly describe how you're using the API and why you need it, and will help the Envato team understand your needs.\n\nYou can specify a user agent when instantiating your client via the `userAgent` option. Here's a great example:\n\n```js\nnew Envato.Client({\n    token: 'personal token',\n    userAgent: 'License activation \u0026 forums for Avada at themefusion.com'\n})\n```\n\n#### Request options\n\nThis library uses [`node-fetch`](https://www.npmjs.com/package/node-fetch) under the hood for its HTTP requests. You can use the `http` option to customize the default timeout and compression settings.\n\n```js\nnew Envato.Client({\n    token: 'personal token',\n    http: {\n        timeout: 30000,\n        compression: false\n    }\n})\n```\n\n#### Rate limiting\n\nThe `handleRateLimits` option controls how the client responds to rate limiting. If set to `true` (default), the client will silently handle rate limit errors by pausing subsequent requests until the rate limit expires, as well as retrying the failed request automatically. This process is entirely behind-the-scenes and requires no extra implementation from you.\n\nSetting this option to `false` will disable silent rate limit handling, and instead will throw `Envato.TooManyRequests` errors when rate limits are encountered. You will need to manually throttle your requests.\n\n#### Concurrency\n\nThe `concurrency` option controls how many active requests the client can have at a single time. The default value is `3`, which means if you send four requests at the same time, only the first three will execute initially, and the fourth will be deferred until one of the first three completes.\n\nSetting this option to `0` will disable throttling, meaning requests will always be executed immediately, and `handleRateLimits` will be forcefully disabled for safety reasons.\n\n## Sending requests\n\nThere are four endpoint helpers for sending requests. Each is a class accessible as a property of the client:\n\n- Catalog – `client.catalog`\n- User – `client.user`\n- Private user – `client.private`\n- Statistics – `client.stats`\n\nWhen sending a request, you will receive a `Promise` instance in return. I recommend using the `await` syntax, but you can also do it the old fashioned way as shown in the section below.\n\nNote that the return values from this wrapper won't always perfectly match the responses from the API. This wrapper tries to provide consistent types, so check the code samples below or refer to your editor's type hinting to determine return types.\n\n### Using promises\n\nThe code samples in this documentation use the `await` operator for simplicity and convenience. However, you can still use promises the old-fashioned way if you'd like. The code snippet below shows both methods.\n\n```js\n// With await operator (recommended)\ntry {\n    const username = await client.private.getUsername();\n    console.log(`Hello, ${username}!`);\n}\ncatch (error) {\n    console.error('Failed to get username:', error.message, error.response);\n}\n\n// With classic promises\nclient.private.getUsername().then(function(username) {\n    console.log(`Hello, ${username}!`);\n}).catch(function(error) {\n    console.error('Failed to get username:', error.message, error.response);\n});\n```\n\n### Getting a user's identity\n\nYou may wish to retrieve a unique ID for the user's account under which you can store data in your own database. Or, you may need to check the permissions on a personal token. You can check both of these through the `getIdentity` method.\n\n```js\nconst { userId, scopes } = await client.getIdentity();\n\nconsole.log('Id:', userId);\nconsole.log('Scopes:', scopes);\n```\n\n```js\nId: 122459\nScopes: [\n    'default',\n    'user:username',\n    'user:email',\n    'sale:verify'\n]\n```\n\n### Manually sending requests\n\nThe client exposes several different methods for sending requests (one for each of get, post, put, patch, and delete).\n\nIn the example below, we send a `POST` request to `/v3/path/to/endpoint` with the `params` object as our encoded form body. The return value is a parsed object.\n\n```js\nconst params = { name: 'hello world', id: 24596 };\nconst response = await client.post('/v3/path/to/endpoint', params);\n```\n\nFor TypeScript users, it is possible to use generics to specify the format of the return value:\n\n```ts\ninterface Response {\n    id: number;\n    name: string;\n    private: boolean;\n};\n\nconst response = await client.get\u003cResponse\u003e('/v3/endpoint');\nconst id = response.id; // Hinted as a number\nconst private = response.private; // Hinted as a boolean\n```\n\n### Catalog\n\n#### Look up a public collection\n\nReturns details of, and items contained within, a public collection. Returns `undefined` if the collection is not found.\n\n```js\nconst response = await client.catalog.getCollection(25043);\n\nconst name = response.collection.name;\nconst items = response.items;\n```\n\n#### Look up a single item\n\nReturns all details of a particular item on Envato Market. Returns `undefined` if the item is not found.\n\n```js\nconst item = await client.catalog.getItem(123456);\n\nconst itemId = item.id;\nconst itemName = item.name;\nconst itemAuthor = item.author_username;\n```\n\n#### Look up a WordPress theme or plugin's version\n\nReturns the latest available version of a theme/plugin. This is the recommended endpoint for WordPress theme/plugin authors building an auto-upgrade system into their item that needs to check if a new version is available. Returns `undefined` if the theme or plugin is not found.\n\n```js\nconst version = await client.catalog.getItemVersion(123456);\n\nconst themeVersion = version.wordpress_theme_latest_version;\nconst pluginVersion = version.wordpress_plugin_latest_version;\n```\n\n#### Search for items\n\nSearches the marketplaces for items, with the same engine as the search on the market sites. Pass an object containing parameters as seen on the [API docs](https://build.envato.com/api#search_GET_search_item_json).\n\n```js\nconst response = await client.catalog.searchItems({\n    term: 'seo',\n    site: 'codecanyon.net',\n    page_size: 100,\n    page: 3\n});\n\nconsole.log('Search took %d milliseconds', response.took);\nconsole.log('Found %d items', response.matches.length);\n\nfor (const item of response.matches) {\n    console.log('Item %d: %s', item.id, item.name);\n}\n```\n\n#### Search for comments\n\nSearches for comments on a specific item.\n\n```js\nconst response = await client.catalog.searchComments({\n    item_id: 123456,\n    sort_by: 'newest',\n    page_size: 100,\n    page: 1\n});\n\nconsole.log('Search took %d milliseconds', response.took);\nconsole.log('Found %d comments', response.matches.length);\n```\n\n#### Get more like this\n\nReturns similar items.\n\n```js\nconst response = await client.catalog.getMoreLikeThis({\n    item_id: 123456,\n    page_size: 100,\n    page: 1\n});\n\nconsole.log('Similar items:', response.matches);\n```\n\n#### Get popular items by site\n\nReturns the popular files for a particular site.\n\n```js\nconst popular = await client.catalog.getPopularItems();\n\nconst lastWeek = popular.items_last_week;\nconst lastThreeMonths = popular.items_last_three_months;\nconst authorsLastMonth = popular.authors_last_month;\n```\n\n#### Get categories by site\n\nLists the categories of a particular site.\n\n```js\nconst categories = await client.catalog.getCategories('codecanyon');\n\nfor (const category of categories) {\n    console.log('%s (%s)', category.name, category.path);\n}\n```\n\n#### Get prices for an item\n\nReturn available licenses and prices for the given item ID. Throws an error if the item is not found.\n\n```js\nconst prices = await client.catalog.getItemPrices(123456);\n\nfor (const entry of prices) {\n    console.log('%s ($%s)', entry.license, entry.price);\n}\n```\n\n```\nRegular License ($12.00)\nExtended License ($125.00)\n```\n\n#### Get new items by site and category\n\nNew files, recently uploaded to a particular site.\n\n```js\nconst items = await client.catalog.getNewFiles('codecanyon', 'php-scripts');\n\nfor (const item of items) {\n    console.log(item.id, item.name);\n}\n```\n\n#### Find featured items and authors\n\nShows the current site features.\n\n```js\nconst features = await client.catalog.getFeatures('codecanyon');\n\nconst featuredMonthlyFile = features.featured_file;\nconst featuredAuthor = features.featured_author;\nconst freeMonthlyFile = features.free_file;\n```\n\n#### Get random new items\n\nShows a random list of newly uploaded files from a particular site (i.e. like the homepage). This doesn't actually appear to be random at this time, but rather sorted by newest first. Could change in future since the endpoint is officially documented to return random results.\n\n```js\nconst items = await client.catalog.getRandomNewFiles('codecanyon');\n\nfor (const item of items) {\n    console.log(item.id, item.name);\n}\n```\n\n### User\n\n#### List a user's collections\n\nLists all of the user's private and public collections.\n\n```js\nconst collections = await client.user.getCollections();\n\nfor (const collection of collections) {\n    console.log(collection.id, collection.name);\n}\n```\n\n#### Look up a user's private collection\n\nReturns details and items for public or the user's private collections. Returns `undefined` if the collection is not\nfound.\n\n```js\nconst { collection, items } = await client.user.getPrivateCollection(123456);\n\nconsole.log('Collection:', collection.id, collection.name);\n\nfor (const item of items) {\n    console.log('Item:', item.id, item.name);\n}\n```\n\n#### Get a user's profile details\n\nShows username, country, number of sales, number of followers, location and image for a user.\n\n```js\nconst user = await client.user.getAccountDetails('baileyherbert');\n\nconsole.log('Username:', user.username);\nconsole.log('Sales:', user.sales);\nconsole.log('Avatar URL:', user.image);\n```\n\n#### List a user's badges\n\nShows a list of badges for the given user.\n\n```js\nconst badges = await client.user.getBadges('baileyherbert');\n\nfor (const badge of badges) {\n    console.log('Badge:', badge.name, badge.label, badge.image);\n}\n```\n\n#### Get a user's item count per marketplace\n\nShow the number of items an author has for sale on each site.\n\n```js\nconst counts = await client.user.getItemsBySite('baileyherbert');\n\nfor (const count of counts) {\n    console.log('The user has %d items on %s', count.items, count.site);\n}\n```\n\n#### Get a user's newest items\n\nShows up to 1000 newest files uploaded by a user to a particular site.\n\n```js\nconst items = await client.user.getNewItems('baileyherbert', 'codecanyon');\n\nfor (const item of items) {\n    console.log(item.id, item.name);\n}\n```\n\n### Private user\n\n#### List an author's sales\n\nLists all unrefunded sales of the authenticated user's items listed on Envato Market. Author sales data (\"Amount\") is reported before subtraction of any income taxes (eg US Royalty Withholding Tax).\n\n```js\nconst sales = await client.private.getSales();\n\nfor (const sale of sales) {\n    console.log('Sold %s for $%s', sale.item.name, sale.amount);\n}\n```\n\n#### Look up a sale by code\n\nReturns the details of an author's sale identified by the purchase code. Author sales data (\"Amount\") is reported before subtraction of any income taxes (eg US Royalty Withholding Tax).\nReturns `undefined` if the sale is not found.\n\n```js\nconst sale = await client.private.getSale('purchase-code');\n\nconsole.log('Buyer:', sale.buyer);\nconsole.log('Item:', sale.item.id, sale.item.name);\n```\n\n#### List all purchases\n\nList purchases that the user has made on their account.\n\n```js\nconst purchases = await client.private.getPurchases();\n```\n\n#### List a buyer's purchases\n\nLists all purchases that the authenticated user has made of the app creator's listed items. Only works with OAuth tokens.\n\n```js\nconst { purchases } = await client.private.getPurchasesFromAppCreator();\n\nfor (const purchase of purchases) {\n    console.log('Bought %s (purchase code: %s)', purchase.item.name, purchase.code);\n}\n```\n\n#### Look up a purchase by code\n\nReturns the details of a user's purchase identified by the purchase code. Returns `undefined` if the purchase is not\nfound.\n\n```js\nconst purchase = await client.private.getPurchase('purchase-code');\n```\n\n#### Get a user's account details\n\nReturns the first name, surname, earnings available to withdraw, total deposits, balance (deposits + earnings) and country.\n\n```js\nconst account = await client.private.getAccountDetails();\n```\n\n#### Get a user's username\n\nReturns the currently logged in user's Envato Account username as a string.\n\n```js\nconst username = await client.private.getUsername();\n```\n\n#### Get a user's email\n\nReturns the currently logged in user's email address as a string.\n\n```js\nconst email = await client.private.getEmail();\n```\n\n#### Get an author's monthly sales\n\nReturns the monthly sales data, as displayed on the user's earnings page. Monthly sales data (\"Earnings\") is reported before subtraction of any income taxes (eg US Royalty Withholding Tax).\n\n```js\nconst sales = await client.private.getMonthlySales();\n```\n\n#### Get statement data\n\nLists transactions from the user's statement page.\n\n```js\nconst response = await client.private.getStatement();\n```\n\n#### Download a purchase\n\nDownload purchased items by either the item_id or the purchase_code. Each invocation of this endpoint will count against the items daily download limit. Returns a string containing the one-time download link.\n\n```js\nconst url = await client.private.getDownloadLink({\n    item_id: 123456\n});\n```\n\n### Statistics\n\n#### Get the total number of market users\n\nReturns the total number of subscribed users to Envato Market.\n\n```js\nconst users = await client.stats.getTotalUsers();\n```\n\n#### Get the total number of market items\n\nReturns the total number of items available on Envato Market.\n\n```js\nconst items = await client.stats.getTotalItems();\n```\n\n#### Get the number of items per category\n\nReturns the total number of items available on Envato Market.\n\n```js\nconst categories = await client.stats.getFilesPerCategory();\n```\n\n## Error handling\n\nWhen sending a request, you should always be prepared to handle errors in the case of an invalid token or server outage. If you're using the `await` operator, then you should surround your calls in a try/catch block:\n\n```js\ntry {\n    const email = await client.private.getEmail();\n}\ncatch (error) {\n    if (error instanceof Envato.HttpError) {\n        console.error('Status:', error.code);\n        console.error('Message:', error.message);\n        console.error('More details:', error.response);\n    }\n    else if (error instanceof Envato.OAuthError) {\n        console.error('Failed to renew OAuth session.');\n    }\n    else {\n        console.error('Got an error from request.');\n    }\n}\n```\n\nAs shown in the code above, requests can throw 3 different types of errors. The first is an instance of `Envato.HttpError`, which will contain a message such as \"Unauthorized\" or \"Bad Request\". It will also usually provide a `response` object which will look something like below, though under rare circumstances this object may be `undefined`:\n\n```json\n{\n    \"error\": 400,\n    \"description\": \"An optional description of the error here\"\n}\n```\n\nThe second type of error is an `Envato.OAuthError`, which only applies to OAuth-based clients, and will be thrown when renewal of the OAuth session fails (which might happen if the user revokes your application's access to their account, or if there's an API outage). This error will contain an `http` property that will expose the underlying `Envato.HttpError` if applicable.\n\nThe third type of error is an `Envato.FetchError` due to a connection error or timeout.\n\n### Error codes\n\n| Code | Object                        | Meaning                                                        |\n|------|-------------------------------|----------------------------------------------------------------|\n| 400  | `Envato.BadRequestError`      | A parameter or argument in the request was invalid or missing. |\n| 401  | `Envato.UnauthorizedError`    | The token is invalid or expired.                               |\n| 403  | `Envato.AccessDeniedError`    | The token does not have the required permissions.              |\n| 404  | `Envato.NotFoundError`        | The requested resource was not found.                          |\n| 429  | `Envato.TooManyRequestsError` | You're sending too many requests, slow down.                   |\n| 500  | `Envato.ServerError`          | API is down or under maintenance, try again later.             |\n\n### Not found errors\n\nFor built-in endpoints that request a specific resource, this library will catch 404 errors and instead return `undefined` if the resource is not found.\nHowever, when sending requests manually, you will need to catch and handle any `Envato.NotFoundError` errors yourself.\n\n- For instance, looking up a purchase code or an item will return `undefined` if the resource is not found.\n- On the other hand, attempting to download an item or retrieve an item's prices will reject with a `NotFoundError` if the item doesn't exist.\n\n## Events\n\nThe client exposes events that you can listen to using the `on()` and `once()` methods.\n\n### Renew\n\nThis event is triggered whenever the client generates a new access token due to the previous token becoming expired. The refresh token always stays the same. This only applies to clients using OAuth.\n\n```js\nclient.on('renew', function(data) {\n    const newAccessToken = data.token;\n    const expiration = data.expiration;\n\n    // Store the new data somewhere...\n    // Note: The refresh token never changes\n});\n```\n\n### Debug\n\nThis event is triggered once for each request. You can use this to debug problems and see what's going on under the hood, and to log things such as status codes or response times.\n\n```js\nclient.on('debug', function(response) {\n    console.log(\n        '[Request] Got %d response back from %s (took %d ms):',\n        response.status,\n        response.request.url,\n        response.took,\n        response.body\n    );\n});\n```\n\n### Ratelimit\n\nThis event is triggered when the client receives a rate limit error from the API and starts pausing requests until the rate limit expires. It passes a single argument, the duration of the rate limit in milliseconds. During this time, any requests you try to send with the client will be held until the duration expires. This event is only triggered if [`handleRateLimits`](#rate-limiting) is set to `true` (the default).\n\n```js\nclient.on('ratelimit', function(duration) {\n    console.log('Rate limited for %d milliseconds.', duration);\n});\n```\n\n### Resume\n\nThis event is triggered after a rate limit expires and the client begins sending requests again. This event is only triggered if [`handleRateLimits`](#rate-limiting) is set to `true` (the default).\n\n```js\nclient.on('resume', function() {\n    console.log('Rate limit has ended.');\n});\n```\n\n## Sandboxing\n\nThe client has a `sandbox` option which allows you to use my unofficial public sandbox. Currently, only a few endpoints are supported:\n\n- Looking up a sale by purchase code\n- Listing all purchases of the app creator's items\n- Get the user's email address\n- Get the user's username\n\nTo use it, simply set the option to true:\n\n```ts\nnew Envato.Client({\n    token: 'your personal token',\n    sandbox: true\n});\n```\n\nYou can test by looking up a sale for the following purchase code:\n\n```\n86781236-23d0-4b3c-7dfa-c1c147e0dece\n```\n\nYou can also specify a base URL to use for a custom sandbox.\n\n```ts\nnew Envato.Client({\n    token: 'your personal token',\n    sandbox: 'http://localhost:8080/'\n});\n```\n\n\u003e 💡 **Note:** When sandbox mode is enabled, the client will automatically remove your personal token and user agent from the request, and a warning will be printed to the console for each request telling you that sandboxing is active.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbaileyherbert%2Fenvato.js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbaileyherbert%2Fenvato.js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbaileyherbert%2Fenvato.js/lists"}