This is a simple wrapper client for the amoCRM REST API. It covers almost all API modules and endpoints. Also, it manages to token refreshing and webhook handling.
⚠️ Due to awful API documentation with tons of mistakes, inaccuracies, examples mismatch and wrong types, lib may provide wrong typing (pls consider to make a PR or issue at least).
-
- NPM & Node support
- examples (not so much)
- maybe some test (webhook atm)
- readme (draft)
-
- Account
- Leads
- Unsorted
- Pipelines and Stages
- Contacts
- Companies
- Catalogs
- Products
- Links
- Tasks
- Custom Fields
- Tags
- Events
- Notes
- Customers
- Statuses
- Segments
- Users
- Webhooks
- Widgets
- Calls
- Talks
- Sources
- Salesbot (api method)
- Short Links
- Chat Templates
- Files
- Chats
-
- Filter builder
- Webhook handling
- Error handling
- Salesbot interactions
Node.JS (versions >=18 are supported because of Fetch API)
npm i @shevernitskiy/amo
import { Amo } from "https://deno.land/x/amo/mod.ts";
Here is the basic usage scenario. We use previously saved token object here (cause it valid for a long time, so we do not need to refresh it often). More examples.
import { readFileSync, writeFileSync } from "node:fs";
import { Amo, ApiError, AuthError } from "@shevernitskiy/amo";
try {
const auth = {
client_id: "1111-2222-3333",
client_secret: "myclientsecret",
redirect_uri: "https://myredirect.org",
};
const token = JSON.parse(readFileSync("./token.json", "utf-8"));
const amo = new Amo("mydomain.amocrm.ru", { ...auth, ...token }, {
on_token: (new_token) => {
console.log("New token obtained", new_token);
writeFileSync("./token.json", JSON.stringify(new_token, null, 2), "utf8");
},
});
const data = await amo.account.getAccount({
with: ["amojo_id"],
});
console.log(data);
} catch (err) {
if (err instanceof ApiError || err instanceof AuthError) {
console.error(err.response);
} else {
console.error(err);
}
}
To create a client instance, you should provide 2 or 3 args to the constructor:
- API domain
- auth data (may be different)
- options (optionally)
const amo = new Amo("mydomain.amocrm.ru", auth_object, options_object);
Amo backend limits you to 7 reqs/sec, so the client can manage with it by performing requests concurrently or sequently with delay. By default, lib performs requests concurrently (7reqs/1000ms by default). To setup you own concurrency params use:
concurrent_request: number
- size of concurrent poolconcurrent_timeframe: number
- timeframe for concurrent pool (ms)
If you want to use sequential requests, set request_delay
option param:
request_delay: number
(ms) - you can set it to zero, if you want to perform requests as it is
on_token?: (new_token: OAuth) => void | Promise<void>
- callback, that will be called on new token event (during receiving from a code or refreshing). Lib manages the auth/token stuff for you, but it is strongly recommended to store the new token persistently somewhere you want (fs, db) to provide it on the next app start.on_error?: (error: Error) => void | Promise<void>;
- default error handler. If provided, it will be called instead of throwing errors. Request lifycycle will not be interrupted and you receivenull
as a response.
The client can authorize you by both methods: auth code and token data. Also, it refreshes the token automatically on expiration.
Usually, this method is used just once while a fresh app is registered. You provide the code and get the token data.
const amo = new Amo("mydomain.amocrm.ru", {
client_id: "1111-2222-3333",
client_secret: "myclientsecret",
grant_type: "authorization_code",
code: "supersecretcode",
redirect_uri: "https://myredirect.org"
}, {
on_token: (new_token) => console.log("New token obtained", new_token);
},
})
This method is used every time after the first authorization by code. The API does not provide the property
expires_at
, but lib returns it in on_token
callback value. If you are using longlive dev token, just keep
expires_at
blank.
const amo = new Amo("mydomain.amocrm.ru", {
client_id: "1111-2222-3333",
client_secret: "myclientsecret",
redirect_uri: "https://myredirect.org",
token_type: "Bearer",
expires_in: 86400,
access_token: "megatoken",
refresh_token: "antohermegatoken",
expires_at: 1682573043856
}, {
on_token: (new_token) => console.log("New token obtained", new_token);
},
})
The client provides methods by API category. Each category reflects the docs structure (not endpoints, actually - this is one of the strange api architecture things). Here is the schema for calling a method in some category:
client_instance.category.method(...)
So the real world example will:
const lead = await amo.lead.getLeadById(6969);
Some methods can receive typical request parameters: order, with, page, limit and filter
Can take array of strings.
with: ["drive_url", "amojo_id", "amojo_rights", "datetime_settings"]
Can take the object.
order: { param: "id", type: "asc" }
Filter is a complex parameter that depends on the method. Lib provides a filter builder to construct filter queries
depending on the method. Each filter can take different types of input conditions: single (property = value), multi
(property = array of values), range (property = from-to), custom fields* , statuses* (*only for leads as I know?). To
use the filter builder depending on the constraits of the API method, you should pass the callback that receives filter
instance and return it with your params. The instance will be typed, and you will not be able to set the value if it
does not satisfy the method constraints (type of the value will be never
).
filter:
((filter) =>
filter
.single("id", 6969)
.multi("created_by", ["john", "smith"])
.range("closed_at", 2418124812, 123124712712));
The client could handle incoming webhook and acting as event emitter, wich emits typed context to the listener callback depending on the event. To use this possibility, the client provides a typical handler that you could setup to handle incoming http requests.
Handler signature is (maybe i'll add (req, res) type for express enojyers later):
((request: Request) => Promise<Response>);
Webhook handling example. Remember that webhookHandler()
is a function factory, create handler just once and then use
it.
const amo = new Amo("mydomain.amocrm.ru", auth_object, options_object);
amo.on("leads:status", (lead) => console.log(lead.id));
const handler = amo.webhookHandler();
Deno.serve({ port: 4545 }, handler);
The client throws several types of errors:
ApiError
AuthError
HttpError
NoContentError
WebhookError
ApiError
and AuthError
has additional property response with API error message.
Handling is simple:
try {
const amo = new Amo("mydomain.amocrm.ru", auth_object, options_object);
const lead = amo.lead.getLeadById(6969);
} catch (err) {
if (err instanceof AuthError) {
console.error("AuthError", err.response);
} else if (err instanceof ApiError) {
console.error("ApiError", err.response);
} else if (err instanceof NoContentError) {
console.error("NoContentError", err.message);
} else if (err instanceof HttpError) {
console.error("HttpError", err.message);
} else {
console.error("UnknownError", err);
}
}
Also you could use default non-intercepted error handler passed with the options
to client constructor:
const amo = new Amo("mydomain.amocrm.ru", auth_object, {
on_error: (err) => console.error("Amo emits error", err);
});
const lead = amo.lead.getLeadById(6969);
if (lead) {
// do logic
}
Pull request, issues and feedback are very welcome. Code style is formatted with deno fmt.
Copyright 2023, shevernitskiy. MIT license.