According to the best practices in Magento 2 you need to use Repositories to create/save entities. However, there are still some issues with repositories for particular entities, including the SalesRule and that's why I will describe here how to achieve it directly via model (the same way how it's implemented in Magento 2 core).
In your class constructor inject the Rule Factory
protected $ruleFactory
public function __construct(\Magento\SalesRule\Model\RuleFactory $ruleFactory) {
$this->rulesFactory = $ruleFactory
}
After this, inside of the method, where you are going to create the rule, init the rule data, assign the date to the rule model and save the model:
$ruleData = [
"name" => "Buy 3 tee shirts and get the 4th free",
"description" => "Buy 3 tee shirts and get the 4th free",
"from_date" => null,
"to_date" => null,
"uses_per_customer" => "0",
"is_active" => "1",
"stop_rules_processing" => "0",
"is_advanced" => "1",
"product_ids" => null,
"sort_order" => "0",
"simple_action" => "buy_x_get_y",
"discount_amount" => "1.0000",
"discount_qty" => null,
"discount_step" => "3",
"apply_to_shipping" => "0",
"times_used" => "0",
"is_rss" => "1",
"coupon_type" => "NO_COUPON",
"use_auto_generation" => "0",
"uses_per_coupon" => "0",
"simple_free_shipping" => "0",
"customer_group_ids" => [0, 1, 2, 3],
"website_ids" => [1],
"coupon_code" => null,
"store_labels" => [],
"conditions_serialized" => '',
"actions_serialized" => ''
];
$ruleModel = $this->ruleFactory->create();
$ruleModel->setData($ruleData);
$ruleModel->save();
All conditions and actions can be assigned via conditions_serialized and actions_serialized fields. They are empty in this example since they have large values usually. To get the corresponding serialized values for your rule go to admin panel, create a rule with all values you want, save it and check the POST parameters of the save request. You will be able to extract the serialized values from the POST request.