Development of JSON API for 1C-Bitrix Turnkey — Strict Contract and Caching
We develop JSON API for 1C-Bitrix — not just "endpoints returning JSON", but a strict contract according to the jsonapi.org specification. With it, a client familiar with the standard can integrate without extra documentation. In our practice, this reduces negotiation time by 30% and eliminates ambiguities in data transfer. A typical project includes 10–15 endpoints; development cost varies depending on complexity. At the same time, savings on maintenance amount to up to 40% due to a single contract.
Advantages of Ordering a JSON API
A ready-made solution accelerates frontend development, mobile apps, and CRM integrations. You cease to depend on internal changes to Bitrix components — the API lives its own life. And with tagged cache (tagged cache on OnBeforeIBlockElementUpdate events), server load drops by 3–5 times compared to standard REST methods. JSON API is faster and more predictable than the built-in REST, especially when working with catalogs of 10,000+ products.
JSON API Architecture on Bitrix
Pure PHP implementation on top of the Bitrix kernel. Entry point is a controller outside the component system:
/local/ api/ v1/ router.php — request routing middleware/ AuthMiddleware.php RateLimitMiddleware.php resources/ ProductResource.php — data transformer OrderResource.php controllers/ ProductController.php OrderController.php In router.php, routes are defined. For example, for products and orders:
$router->get('/v1/products', [ProductController::class, 'index']); $router->get('/v1/products/{id}', [ProductController::class, 'show']); $router->post('/v1/orders', [OrderController::class, 'create']); $router->patch('/v1/orders/{id}', [OrderController::class, 'update']); Transforming Data into JSON API
The resource class transforms raw data from information blocks into JSON structure, isolating clients from field changes. Example for products:
class ProductResource { public static function make(array $product, array $include = []): array { $data = [ 'id' => (int)$product['ID'], 'type' => 'products', 'attributes' => [ 'name' => $product['NAME'], 'code' => $product['CODE'], 'description' => $product['DETAIL_TEXT'], 'active' => $product['ACTIVE'] === 'Y', 'created_at' => $product['DATE_CREATE'], ], 'relationships' => [], ]; if (in_array('prices', $include)) { $data['relationships']['prices'] = PriceResource::collection( PriceRepository::getForProduct((int)$product['ID']) ); } if (in_array('sku', $include)) { $data['relationships']['sku'] = SkuResource::collection( SkuRepository::getForProduct((int)$product['ID']) ); } return $data; } } The ?include=prices,sku parameter in the request controls inclusion of related data — the client gets exactly what they need.
Filtering, Sorting, and Pagination
All three mechanisms are implemented via query parameters. They work in a single code block and return metadata on record count.
// Filtering $filter = ['IBLOCK_ID' => CATALOG_IBLOCK_ID, 'ACTIVE' => 'Y']; if (isset($_GET['filter']['section_id'])) { $filter['SECTION_ID'] = (int)$_GET['filter']['section_id']; } // Sorting $sort = []; foreach (explode(',', $_GET['sort'] ?? 'id') as $field) { $direction = str_starts_with($field, '-') ? 'DESC' : 'ASC'; $sort[ltrim($field, '-')] = $direction; } // Pagination (offset-based) $limit = (int)($_GET['page']['size'] ?? 20); $offset = ((int)($_GET['page']['number'] ?? 1) - 1) * $limit; The response includes metadata:
{ "data": [...], "meta": { "total": 1543, "page": 2, "per_page": 20, "last_page": 78 }, "links": { "self": "/v1/products?page[number]=2", "next": "/v1/products?page[number]=3", "prev": "/v1/products?page[number]=1" } } Creating an Order
POST /v1/orders with request body:
{ "data": { "type": "orders", "attributes": { "delivery_address": "Moscow, Pushkina St., 1", "payment_method": "card" }, "relationships": { "items": { "data": [ { "type": "order-items", "product_id": 123, "quantity": 2 }, { "type": "order-items", "product_id": 456, "quantity": 1 } ] } } } } The controller validates data and calls \Bitrix\Sale\Order::create() through the D7 API of the sale module. On error — a 422 Unprocessable Entity response with a structured error list.
Authentication
-
Bitrix session. For requests from browser applications where the user is logged in on the site. We check
\CUser::IsAuthorized(). -
Bearer token (JWT). For mobile clients and server-to-server. Middleware decodes JWT, gets
user_id, initializes Bitrix session:
$userId = $jwt->getClaim('sub'); \CUser::SetCurrent($userId); After that, all standard permission checks work correctly.
-
API Key. For B2B partners. Key in the
X-API-Keyheader, tied to a user or group in Bitrix.
Input Validation
Before passing to modules — strict validation. Each endpoint has a Request class with rules:
class CreateOrderRequest { public function validate(array $data): array { $errors = []; if (empty($data['delivery_address'])) { $errors[] = ['pointer' => '/data/attributes/delivery_address', 'detail' => 'Required field']; } if (!in_array($data['payment_method'] ?? '', ['card', 'cash', 'invoice'])) { $errors[] = ['pointer' => '/data/attributes/payment_method', 'detail' => 'Invalid value']; } return $errors; } } Errors are returned in JSON API Errors format:
{ "errors": [ { "status": "422", "source": { "pointer": "/data/attributes/delivery_address" }, "title": "Validation error", "detail": "Required field" } ] } Response Caching
For GET requests, we set up HTTP cache via headers:
header('Cache-Control: public, max-age=600, s-maxage=3600'); header('ETag: "' . md5($cacheKey . $dataHash) . '"'); On the Bitrix side — tagged cache for aggregated data. When a product is updated from 1C exchange, the tag is invalidated, and the next request fetches fresh data from the database.
What’s Included in the Work
- Resource and endpoint design
- Router, middleware, authentication development
- Catalog resources implementation (Products, SKU, prices, leftovers)
- Commerce operations (cart, orders, payment)
- User endpoints (auth, profile, order history)
- Caching (HTTP headers, Redis, tagged cache)
- OpenAPI documentation + Postman collection
- Integration tests and load testing
- Code in Git, deployment instructions
Development Stages
| Stage | Content | Duration |
|---|---|---|
| Design | Resources, endpoints, data format | 1 week |
| Infrastructure | Router, middleware, authentication | 1 week |
| Catalog resources | Products, SKU, prices, leftovers, sections | 1–2 weeks |
| Commerce operations | Cart, orders, payment | 1–2 weeks |
| User endpoints | Auth, profile, order history | 1 week |
| Caching | HTTP headers, Redis, tagged cache | 1 week |
| Documentation | OpenAPI, Postman collection | 3–5 days |
| Testing | Integration tests, load testing | 1 week |
Cache Architecture Details
We use Bitrix tagged cache: when an information block element is saved, the `OnAfterIBlockElementAdd` event is triggered, which invalidates the cache by the `iblock_id_XXX` tag. This guarantees data freshness without manual reset.JSON API on Bitrix is a strict, predictable contract that lives independently of component versions and templates. With proper implementation, the frontend team works with the API as an independent service. All requests are logged, errors are returned in a standard format, and versioning protects clients from unexpected schema changes.
Evaluate your project for free. Contact us — we will analyze the requirements and propose an architecture with timelines. Get a consultation from an engineer with over 10 years of Bitrix experience.







