Creatio
An idiomatic, full-featured Elixir SDK for the Creatio CRM (formerly bpm'online) API.
Built on top of Req, Creatio provides a clean and resilient interface for OData 4, OData 3, DataService JSON APIs, Forms Authentication, and OAuth 2.0 Client Credentials.
Features
- Authentication:
- OAuth 2.0: Client credentials grant against Creatio Identity Service (
/connect/token). - Forms Authentication: Session cookies (
.ASPXAUTH,BPMLOADER) with automaticBPMCSRFextraction and URI decoding. - OAuth Health Checks: Endpoint checking (
/0/api/OAuthHealthCheck) and OpenID configuration discovery.
- OAuth 2.0: Client credentials grant against Creatio Identity Service (
- Defense-in-Depth Read-Only Mode:
- Enforce read-only safety at the client level (
read_only: trueorCREATIO_READ_ONLY=true). - Locally blocks mutation operations (
create,update,delete, field mutations, mutating batches, and DataService writes) with structured 403ReadOnlyModeerrors.
- Enforce read-only safety at the client level (
- OData 4 Support (
/0/odata/):- Collection listing with
$select,$filter,$orderby,$top,$skip,$expand, and inline$count. - CRUD operations (
create,get,update,delete). - Field-level operations and scalar
$valueaccess. - Binary stream / file uploads via
PUTwithapplication/octet-stream. - Composable Query Builder (
Creatio.OData.Query). - Lazy auto-paginated streaming (
Creatio.stream/3) with configurable page sizes and initial:skipoffsets.
- Collection listing with
- OData 4 Batch Operations (
/0/odata/$batch):- Full support for JSON batch requests, atomicity groups (changesets), and
Prefer: continue-on-error.
- Full support for JSON batch requests, atomicity groups (changesets), and
- DataService API:
- Direct execution of Creatio
SelectQuerypayloads. - Calling custom REST services (
/0/rest/...).
- Direct execution of Creatio
- OData 3 Support:
- Legacy
EntityDataService.svcendpoint compatibility with streaming support.
- Legacy
- Test-Ready:
- Easily mockable using standard
Reqplug options (req_options: [plug: ...]).
- Easily mockable using standard
Installation
Add creatio to your list of dependencies in mix.exs:
def deps do
[
{:creatio, "~> 0.3.1"}
]
end
Or when developing locally in the same repo:
def deps do
[
{:creatio, path: "creatio"}
]
end
Configuration & Client Initialization
Initialize a client by specifying your Creatio base URL:
client = Creatio.new(base_url: "https://myinstance.creatio.com")
You can also configure global defaults in config/config.exs or via environment variables:
config :creatio,
base_url: "https://myinstance.creatio.com",
auth_token: System.get_env("CREATIO_BEARER_TOKEN"),
read_only: System.get_env("CREATIO_READ_ONLY") in ["true", "1"]
Client Options
| Option | Env Var | Description |
|---|---|---|
:base_url |
CREATIO_BASE_URL |
Base URL of your Creatio CRM instance |
:auth_token |
CREATIO_AUTH_TOKEN / CREATIO_BEARER_TOKEN |
Bearer access token for OAuth 2.0 |
:identity_service_url |
CREATIO_IDENTITY_URL / CREATIO_IDENTITY_SERVICE_URL |
Identity Service URL |
:client_id |
CREATIO_CLIENT_ID |
OAuth 2.0 client ID |
:client_secret |
CREATIO_CLIENT_SECRET |
OAuth 2.0 client secret |
:read_only |
CREATIO_READ_ONLY |
Defense-in-depth read-only mode (true or false) |
:cookie |
- | Session cookie string for Forms auth |
:bpmcsrf |
- | CSRF token string for Forms auth |
:req_options |
- | Custom options forwarded to Req.new/1 (e.g., plugs, retries) |
Defense-in-Depth Read-Only Mode
When performing data migrations, read replicas, analytics pipelines, or background synchronization jobs, you can enforce strict read-only safety to mathematically guarantee that your application never mutates production CRM records:
# Explicitly initialize in read-only mode:
client = Creatio.new(
base_url: "https://myinstance.creatio.com",
read_only: true
)
# Queries and read operations succeed normally:
{:ok, contacts} = Creatio.list(client, "Contact")
{:ok, count} = Creatio.count(client, "Contact")
{:ok, res} = Creatio.select_query(client, %{"RootSchemaName" => "Contact"})
# Any mutation is intercepted locally before sending over the network:
{:error, %Creatio.Error{status: 403, code: "ReadOnlyMode"}} =
Creatio.create(client, "Contact", %{"Name" => "Test"})
{:error, %Creatio.Error{status: 403, code: "ReadOnlyMode"}} =
Creatio.update(client, "Contact", contact_id, %{"JobTitle" => "VP"})
{:error, %Creatio.Error{status: 403, code: "ReadOnlyMode"}} =
Creatio.delete(client, "Contact", contact_id)
Read-only mode enforcement includes:
- SDK-level guards:
create/4,update/5,delete/4,update_field/6,delete_field/5, and DataServiceinsert_query/3,update_query/3,delete_query/3fail immediately without dispatching HTTP requests. - Batch validation: Batches with mutation operations (
POST,PATCH,PUT,DELETE) or binary batch requests are rejected. - HTTP request-step middleware: An appended Req request step blocks any raw mutation HTTP requests from leaving the client, while explicitly allowing safe paths (
/SelectQuery,/Login,/token,/AuthService.svc/Login).
Authentication
1. Forms Authentication (Username / Password)
client = Creatio.new(base_url: "https://myinstance.creatio.com")
{:ok, client} = Creatio.login(client, "IntegrationUser", "SecurePassword123")
2. OAuth 2.0 (Identity Service)
Recommended for server-to-server synchronization:
client = Creatio.new(
base_url: "https://myinstance.creatio.com",
identity_service_url: "https://myidentity.creatio.com",
client_id: "your_client_id",
client_secret: "your_client_secret"
)
{:ok, client, token_info} = Creatio.login_oauth(client)
Verify OAuth functionality at any time:
{:ok, status} = Creatio.oauth_health_check(client)
OData 4 Operations
Querying Records
# Simple list
{:ok, contacts} = Creatio.list(client, "Contact",
select: ["Id", "Name", "Email"],
filter: "Age gt 18",
orderby: "Name asc",
top: 10
)
# Request total count inline
{:ok, %{records: contacts, count: total_count}} =
Creatio.list(client, "Contact", select: ["Id", "Name"], count: true)
Composable Query Builder
query =
Creatio.query("Contact")
|> Creatio.OData.Query.select(["Id", "Name", "MobilePhone"])
|> Creatio.OData.Query.filter_gt("Age", 21)
|> Creatio.OData.Query.filter_contains("Name", "Smith")
|> Creatio.OData.Query.filter_eq("City/Name", "Toronto")
|> Creatio.OData.Query.order_by("CreatedOn desc")
|> Creatio.OData.Query.top(25)
|> Creatio.OData.Query.skip(50)
|> Creatio.OData.Query.expand("Account")
{:ok, contacts} = Creatio.list(client, query)
The query builder supports all standard OData 4 filtration operators and functions:
filter_eq/3,filter_ne/3,filter_gt/3,filter_ge/3,filter_lt/3,filter_le/3filter_contains/3,filter_startswith/3,filter_endswith/3filter_day/3,filter_length/3,filter_not/1filter_in/3,filter_in_ids/3(formats unquoted GUID ORs for Creatio)or_filter_eq/3,or_filter_contains/3,or_filter_startswith/3,or_filter_endswith/3
Automatic Streaming / Pagination
Stream records without loading all results into memory at once:
Creatio.stream(client, "Contact", page_size: 100, select: ["Id", "Name"])
|> Stream.filter(fn contact -> contact["Name"] != nil end)
|> Enum.take(500)
Resuming Streams with Initial Skip Offset
Creatio.stream/3 and Creatio.odata3_stream/3 accept an initial :skip offset, making it easy to resume interrupted background syncs or stream data in partition windows:
# Resume streaming from offset 1,000
Creatio.stream(client, "Contact", page_size: 100, skip: 1000, select: ["Id", "Name"])
|> Enum.take(200)
# Or combine directly with the Query builder
query =
Creatio.query("Contact")
|> Creatio.OData.Query.select(["Id", "Name"])
|> Creatio.OData.Query.skip(500)
Creatio.stream(client, query, page_size: 100)
CRUD Operations
# Create
{:ok, new_contact} = Creatio.create(client, "Contact", %{
"Name" => "John Doe",
"Email" => "john.doe@example.com"
})
# Retrieve by ID
{:ok, contact} = Creatio.get(client, "Contact", new_contact["Id"])
# Update
:ok = Creatio.update(client, "Contact", contact["Id"], %{
"JobTitle" => "Managing Director"
})
# Delete
:ok = Creatio.delete(client, "Contact", contact["Id"])
Field & Binary Stream Uploads
# Get raw scalar value
{:ok, name} = Creatio.get_field_value(client, "Contact", contact_id, "Name")
# Upload binary / octet-stream (e.g. photo or document)
binary_content = File.read!("avatar.png")
:ok = Creatio.update_field(client, "Contact", contact_id, "Photo", binary_content)
# Delete a field value
:ok = Creatio.delete_field(client, "Contact", contact_id, "Photo")
Batch Requests
Execute multiple operations in a single HTTP request using either the OData 4 JSON batch format or multipart/mixed MIME format:
batch =
Creatio.batch_new(continue_on_error: true)
|> Creatio.Batch.add_create("Contact", %{"Name" => "Alice"}, atomicity_group: "group1")
|> Creatio.Batch.add_update("Contact", contact_id, %{"JobTitle" => "VP"}, atomicity_group: "group1")
|> Creatio.Batch.add_delete("Contact", old_contact_id)
# Execute via JSON batch (default)
{:ok, responses} = Creatio.batch(client, batch)
# Execute via multipart/mixed MIME batch
{:ok, responses} = Creatio.batch(client, batch, format: :multipart)
Enum.each(responses, fn resp ->
IO.puts("Request #{resp.id}: status #{resp.status}")
end)
DataService & Custom Services
Direct execution of Creatio DataService JSON SyncReply endpoints:
# SelectQuery
{:ok, res} = Creatio.select_query(client, %{"RootSchemaName" => "Account"})
# InsertQuery, UpdateQuery, DeleteQuery, BatchQuery
{:ok, res} = Creatio.insert_query(client, %{"RootSchemaName" => "Contact", ...})
{:ok, res} = Creatio.update_query(client, %{"RootSchemaName" => "Contact", ...})
{:ok, res} = Creatio.delete_query(client, %{"RootSchemaName" => "Contact", ...})
{:ok, res} = Creatio.batch_query(client, %{"items" => [...]})
# Custom REST Web Service
{:ok, res} = Creatio.custom_service(client, "CustomService", "Endpoint", %{"param" => "value"})
Legacy OData 3 Support
Full compatibility with legacy Creatio EntityDataService.svc endpoints:
{:ok, contacts} = Creatio.odata3_list(client, "Contact", top: 10, count: true)
{:ok, contact} = Creatio.odata3_get(client, "Contact", contact_id)
{:ok, account} = Creatio.odata3_get_navigation(client, "Contact", contact_id, "Account")
{:ok, name} = Creatio.odata3_get_field(client, "Contact", contact_id, "Name")
{:ok, raw_name} = Creatio.odata3_get_field_value(client, "Contact", contact_id, "Name")
{:ok, count} = Creatio.odata3_count(client, "Contact")
{:ok, created} = Creatio.odata3_create(client, "Contact", %{"Name" => "Legacy"})
:ok = Creatio.odata3_update(client, "Contact", contact_id, %{"Name" => "Updated"})
:ok = Creatio.odata3_delete(client, "Contact", contact_id)
# Legacy OData 3 streaming / auto-pagination
Creatio.odata3_stream(client, "Contact", page_size: 100)
|> Enum.take(250)
API Reference & Postman Collection
Comprehensive API documentation and resources are available in the docs/ directory:
- Creatio API & Integration Guide — Authentication flows, OData 4 protocol endpoints, and entity mapping reference.
- Syncing Creatio Users Guide — A step-by-step tutorial on building a resilient sync engine between Creatio and a local database.
- Postman Collection v2.0 — Full 1.8 MB offline Postman collection covering all Creatio endpoints (OAuth 2.0, OData 4 CRUD, streaming, batching, and OData 3).
- Online Creatio Postman Documentation
- Official Postman Environment
License
Licensed under the Apache License, Version 2.0. See LICENSE for details.