A configured Exact Online API client.
Building a client
With a token you manage yourself:
client = Exact.Client.new(access_token: token, division: 123_456)With a token store, which is what you want in an application because it lets the client refresh and persist the rotating refresh token for you:
client =
Exact.Client.new(
division: 123_456,
token_store: Exact.TokenStore.ETS,
token_key: user_id,
credentials: [
client_id: client_id,
client_secret: client_secret,
redirect_uri: redirect_uri,
region: :nl
]
)Paths
Paths are relative to the division, so "crm/Accounts" becomes
/api/v1/123456/crm/Accounts. A path starting with /api is used as-is,
which is how the division independent endpoints are reached
("/api/v1/current/Me"). A full URL is used as-is too, which is how
stream/3 follows the __next cursor.
Return values
Every function returns {:ok, result} or {:error, %Exact.Error{}}. The
bang variants raise the error instead. list/3 returns an Exact.Page,
get/3 and post/4 return the entity as a plain map with the field names
Exact Online uses, and put/4 and delete/3 return {:ok, nil} because the
API answers those with 204 No Content.
Summary
Functions
Deletes an entity. Returns {:ok, nil}.
Same as delete/3, but raises Exact.Error on failure.
Fetches a single entity.
Same as get/3, but raises Exact.Error on failure.
Fetches one page of a collection.
Same as list/3, but raises Exact.Error on failure.
Builds a client.
Creates an entity.
Same as post/4, but raises Exact.Error on failure.
Updates an entity, sending only the fields you pass.
Same as put/4, but raises Exact.Error on failure.
Returns a copy of client scoped to division.
Runs a request and returns the raw Req.Response.
Same as request/2, but raises Exact.Error on failure.
Streams every record of a collection, following the __next cursor.
Types
@type t() :: %Exact.Client{ base_url: String.t(), division: integer() | String.t() | nil, region: Exact.Region.t() | nil, req: Req.Request.t() }
Functions
@spec delete(t(), String.t(), keyword()) :: {:ok, nil} | {:error, Exact.Error.t()}
Deletes an entity. Returns {:ok, nil}.
Same as delete/3, but raises Exact.Error on failure.
@spec get(t(), String.t(), keyword()) :: {:ok, map() | nil} | {:error, Exact.Error.t()}
Fetches a single entity.
Accepts the Exact.Query options, most usefully :select and :expand.
id = "11111111-2222-3333-4444-555555555555"
Exact.Client.get(client, "crm/Accounts(" <> Exact.Query.guid(id) <> ")")
Same as get/3, but raises Exact.Error on failure.
@spec list(t(), String.t(), keyword()) :: {:ok, Exact.Page.t()} | {:error, Exact.Error.t()}
Fetches one page of a collection.
Exact Online returns 60 records per page by default and 1000 for the bulk and
sync endpoints. Follow page.next or use stream/3.
{:ok, page} = Exact.Client.list(client, "crm/Accounts", select: ["ID", "Name"], top: 10)
@spec list!(t(), String.t(), keyword()) :: Exact.Page.t()
Same as list/3, but raises Exact.Error on failure.
Builds a client.
Options
:region-[:es, :be, :nl, :fr, :de, :uk, :us], defaults to:nl:base_url- overrides:region:division- the division code every relative path is scoped to. Discover it withExact.System.Me.division/1:access_token- use a token directly, without refreshing:token_store- a module implementingExact.TokenStore:token_key- the key the token is stored under, defaults to:default:credentials- the optionsExact.OAuth.refresh/2needs, required together with:token_store:refresh_skew- refresh this many seconds before the token expires, defaults to60:req_options- merged into the underlyingReq.Request, for things likereceive_timeout:or aplug:stub in tests
Creates an entity.
The body is sent as JSON. Exact Online answers with the created entity, which
is the only way to learn its ID.
{:ok, account} = Exact.Client.post(client, "crm/Accounts", %{"Name" => "Paradiso"})
Same as post/4, but raises Exact.Error on failure.
Updates an entity, sending only the fields you pass.
Exact Online answers with 204 No Content, so this returns {:ok, nil}.
Same as put/4, but raises Exact.Error on failure.
Returns a copy of client scoped to division.
@spec request(t(), keyword()) :: {:ok, Req.Response.t()} | {:error, Exact.Error.t()}
Runs a request and returns the raw Req.Response.
Every other function funnels through here. Reach for it when you need response headers, or a method the wrappers do not cover.
@spec request!(t(), keyword()) :: Req.Response.t()
Same as request/2, but raises Exact.Error on failure.
@spec stream(t(), String.t(), keyword()) :: Enumerable.t()
Streams every record of a collection, following the __next cursor.
The stream is lazy and only requests a page when it is consumed, so it stays
cheap to Stream.take/2 from it. It raises Exact.Error on failure,
because a stream has nowhere to put an error tuple.
client
|> Exact.Client.stream("crm/Accounts", select: ["ID", "Name"])
|> Stream.map(& &1["Name"])
|> Enum.take(500)