Babel Licensing API Reference
The Babel Licensing Service exposes a REST API to manage licenses, products, customers, orders, releases and the rest of the licensing data, to run the licensing operations of protected applications, and to serve application updates. This page covers what every endpoint has in common. The pages linked at the bottom list the endpoints one by one.
Base URL and Versioning
Every route starts with the version segment /v1:
https://your-licensing-server/v1/...The host and port come from the Kestrel section of the service appsettings.json; the default configuration listens on https://localhost:5000 (see Kestrel). The code samples in this reference show paths only, so prepend your server address.
The REST API is served only when Application:EnableWebApi is true, the default. With Application:EnableSwagger also enabled, the service publishes the OpenAPI document this reference is generated from at /swagger/v1/swagger.json, and Swagger UI at /swagger.
The service also exposes gRPC services for the runtime operations (Authentication, LicenseServer and ReportServer). The Babel.Licensing client library talks to them over gRPC or over HTTP (see UseHttp vs UseGrpc). The default configuration adds a separate HTTP/2 endpoint for gRPC on port 5005, and gRPC-Web is enabled on the main endpoint (Application:EnableGrpcWeb). See gRPC Web and gRPC SSL/TLS.
Content Types
- Request and response bodies are JSON (
application/json) with camelCase property names. - Dates are UTC strings such as
2026-09-01T00:00:00.000Z. - Enumerations are serialized as numbers, unless
Application:SerializeJSonEnumAsStringis enabled. - Properties with a null value are left out of responses while
Application:IgnoreJSonDefaultValuesistrue, as it is in the default configuration. - A client that sends
Accept: application/toonreceives TOON instead of JSON. Without that header the response is JSON. - Uploading a release artifact uses
multipart/form-data, and the update feed files aretext/yaml. See Product Releases and Updates.
Authentication
The API accepts two credentials. Which one an endpoint takes is shown under each endpoint as Authentication.
| Credential | Header | Obtained from | Used by |
|---|---|---|---|
| API key | x-api-key: <key> | The web application, Babel Desktop, or POST /v1/api-keys | Management API, product releases, release downloads, webhooks; also licensing operations and reports |
| Bearer token (JWT) | Authorization: Bearer <token> | POST /v1/auth/login | Signed-in user routes under /v1/auth; licensing operations and reports |
The header name x-api-key can be changed with Application:ApiKeyHeaderName.
The Management API, product release, release download and webhook routes accept only an API key. A bearer token is refused there with 401, even the token of an administrator.
API keys
An API key belongs to a user, its owner. A request authenticated by the key acts with the roles of that owner, and the key adds its own permission bits on top:
| Permission | Value | Allows |
|---|---|---|
| Read | 1 | GET |
| Write | 2 | PUT, PATCH |
| Delete | 4 | DELETE |
| Create | 8 | POST |
| All | 15 | every method |
The permission follows the HTTP method, not what the route does: POST /v1/products/{productId}/releases/{releaseId}/downloads/verify changes nothing but still needs Create. Permission bits are checked on the Management API, release download and webhook routes. A key without the bit for the method receives 403.
A key is refused with 401 when it is unknown, revoked or past its expiration date. When a key has no owner it carries no role, so every role-protected route answers 403, and POST /v1/api-keys refuses to create one. When the service creates the administrator account at startup (Application:AdminUsername and Application:AdminPassword), it also creates an API key with all permissions for that user.
The service caches a validated key for Application:ApiKeyCacheDuration (3 minutes in the default appsettings.json). A change to a key, such as revoking it or removing a permission, can take that long to apply.
Get an API key
Create the key in the web application, in Babel Desktop, or with POST /v1/api-keys using a key that belongs to an administrator. Give it only the permissions the client needs.
Send it with every request
curl https://your-licensing-server/v1/products?take=10 \
-H 'x-api-key: api_xxxxxxxxxxxxxxxxxxxxxxxx'Check what the key can do
GET /v1/auth/session answers with the owner, the owner’s roles and the key’s permission bits. It needs no permission of its own, so any valid key can call it.
{
"success": true,
"user": "admin",
"roles": [ "Administrator" ],
"permissions": 1,
"keyName": "read-only"
}Bearer tokens
POST /v1/auth/login returns a JWT in token and its lifetime in seconds in expiresIn (Application:TokenExpiration, 30 minutes by default). Send it as Authorization: Bearer <token>.
User name and password
A user signs in with the user name and password. The token carries the roles of the user and gives access to /v1/auth/user, /v1/auth/session, /v1/auth/logout, the licensing operations and reports.
curl -X POST https://your-licensing-server/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"<password>"}'GET /v1/auth/user returns the signed-in user with its roles, settings and active API keys. GET /v1/auth/session works with both credentials: with a bearer token it reports the user and roles, and permissions is absent.
Roles and Access
The service defines the roles Administrator, LicenseManager and Sales, and every Management API route requires one of them. The roles Customer and Application also exist, but no REST route requires them.
| Area | Routes | Credential | Roles |
|---|---|---|---|
| Update feed and feed keys | /v1/updates/..., /v1/extensions/keys | None | Anyone |
| Login | POST /v1/auth/login | None | Anyone |
| Signed-in user | /v1/auth/user, POST /v1/auth/logout | Bearer | Any user |
| Session | GET /v1/auth/session | Bearer or API key | Any |
| Licensing operations | /v1/license/... | Bearer or API key | No role required |
| Reports sent by applications | POST /v1/report | Bearer or API key | No role required |
| Customers, contacts, products, orders, resources | /v1/customers, /v1/contacts, /v1/products, /v1/orders, /v1/resources | API key | Administrator, LicenseManager, Sales |
| Licenses (list, create, update) and license tokens | /v1/licenses, /v1/license-tokens | API key | Administrator, LicenseManager, Sales |
| License templates (list, template licenses) | GET /v1/license-templates... | API key | Administrator, LicenseManager, Sales |
| Product releases, release assemblies and release license templates | /v1/products/{productId}/releases... | API key | Administrator, LicenseManager, Sales |
| Log, server info, email | /v1/log, /v1/info, /v1/email/... | API key | Administrator, LicenseManager, Sales |
| License deletion | DELETE /v1/licenses/{licenseId} | API key | Administrator, LicenseManager |
| License templates (create, update, delete) | POST, PUT, DELETE /v1/license-templates | API key | Administrator, LicenseManager |
| License traces and received reports | /v1/license-traces, /v1/reports | API key | Administrator, LicenseManager |
| Release downloads | /v1/products/{productId}/releases/{releaseId}/downloads... | API key | Administrator, LicenseManager |
| Webhooks | /v1/webhooks/... | API key | Administrator, LicenseManager |
| Users, user settings, roles, API keys | /v1/users, /v1/roles, /v1/api-keys | API key | Administrator |
| Assemblies and service settings | /v1/assemblies, /v1/settings | API key | Administrator |
A request with a valid credential but without the role answers 403.
Anonymous Endpoints
These routes take no credential:
POST /v1/auth/login.- The update feed:
GET /v1/updates/{productCode}, the electron-updater feed files and the artifact download. A caller identifies its license, when it has one, with theX-Babel-User-Keyheader or theuserKeyquery parameter. See Updates. GET /v1/extensions/keys, the public keys that sign the feed.- The health check at
/health(Application:HealthCheckEndPoint), whenApplication:EnableHealthCheckistrue. It is not part of the OpenAPI document; see Health Check.
The licensing operations under /v1/license are not anonymous. An application that has only its user key signs in with it first, as shown in Bearer tokens.
Querying Lists
Most GET routes that return a list accept the same query parameters:
| Parameter | Meaning | Example |
|---|---|---|
filter | A Dynamic LINQ expression on the entity properties | userKey="AAAAA-BBBBB-CCCCC-DDDDD" |
sort | Comma-separated properties; prefix - for descending, + or nothing for ascending | -createdAt,name |
skip | Number of items to skip | 20 |
take | Maximum number of items to return | 10 |
select | Comma-separated properties to return | id,code,name |
include | Comma-separated related entities to load, where the route supports it | Contacts |
With skip or take and no sort, results are ordered by primary key, so pages stay stable. The response carries the page of items and a total count of the items that match filter, such as totalProductsCount. Each endpoint page lists the parameters that route accepts: the release download list, for example, has no select, and the token lists add countOnly.
URL-encode the values. curl -G with --data-urlencode does it for you:
# Products whose code starts with "babel", newest first, three properties only
curl -G https://your-licensing-server/v1/products \
-H 'x-api-key: <key>' \
--data-urlencode 'filter=code.StartsWith("babel")' \
--data-urlencode 'select=id,code,name' \
--data-urlencode 'sort=-id' \
--data-urlencode 'take=10'
# The license with a given user key
curl -G https://your-licensing-server/v1/licenses \
-H 'x-api-key: <key>' \
--data-urlencode 'filter=userKey="AAAAA-BBBBB-CCCCC-DDDDD"'
# A customer with its contacts
curl -G https://your-licensing-server/v1/customers \
-H 'x-api-key: <key>' \
--data-urlencode 'filter=id=42' \
--data-urlencode 'include=Contacts'A filter that names an unknown property is not a validation error: the service answers 500 with the parser message, for example No property or field 'nope' exists in type 'Product'.
Updating Resources
PUT requests perform partial updates: a field omitted from the request body keeps its stored value. A value you send explicitly, including false or 0, is applied. Send only the fields you want to change.
This applies to licenses, license templates, customers, contacts, products, product releases, order products, API keys and webhook events.
Before 12.0 an omitted Boolean, number or enumeration field was reset to false/0. For example, updating a license without revoked un-revoked it. Clients that relied on that reset must now send the value explicitly.
When a product release is created without requiresMaintenance, it defaults to true, so the release is available only to licenses in maintenance. For an application product the service refuses requiresMaintenance: false unless Updates:AllowPublicApplicationReleases is enabled. See Releases.
Errors
Error responses come in three shapes.
Service errors
Every route except the anonymous update routes reports errors with a small JSON body:
{ "success": false, "code": 400, "message": "Product 999 not found." }code repeats the HTTP status. The status depends on the error:
400for a request the client can correct, such as a missing or invalid field. Many “not found” cases on the Management API also answer400, with the reason inmessage.401for a failed login (Invalid username or password.,Invalid user key.).500for anything else, including a failed database operation.520plus the gRPC status code for the licensing errors shared with the gRPC services. The bodymessagenames the error. See Error Codes.
| HTTP status | gRPC status | Example message |
|---|---|---|
| 523 | InvalidArgument | Invalid license key; The machine code is required for license activation |
| 525 | NotFound | License key not found; License token not found |
| 526 | AlreadyExists | The license has already been activated |
| 527 | PermissionDenied | The license has been revoked; The license has expired |
| 528 | ResourceExhausted | The maximum number of concurrent users for the license has been reached |
| 529 | FailedPrecondition | The license has not been activated |
| 535 | DataLoss | The license has been tampered |
Validation problems
Validation problems use the standard ASP.NET Core application/problem+json body. You get one when the request body cannot be read, for example malformed JSON:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": { "": [ "Unexpected end while parsing unquoted property name. Path '', line 1, position 4." ] }
}Update route errors
The update routes use the { success, code, message } body too, but with ordinary HTTP statuses only: 400 for an unknown platform, channel or version, 403 for a revoked or expired license or a refused download, 404 for an unknown product or artifact. They never answer 52x.
Status codes
| Status | Meaning |
|---|---|
400 | Invalid request, or an entity referenced by the request does not exist |
401 | No credential, an invalid or expired credential, a bearer token on an API-key-only route, or a failed login. Authorization failures have an empty body |
403 | Valid credential without the required role or API key permission; on update routes, a refused license or download |
404 | Unknown route; on update routes, an unknown product or artifact; on webhook routes, an unknown subscription or event |
429 | Too many requests from the client IP; see Rate Limiting |
500 | Unexpected error, invalid filter expression, or a login from an IP blocked after failed attempts |
52x | Licensing error mapped from a gRPC status (table above) |
Rate Limiting
The IpFiltering section of appsettings.json protects the service. The defaults are:
| Setting | Default | Effect |
|---|---|---|
BruteForceProtection:Enabled | true | Counts requests per client IP |
BruteForceProtection:MaxRequestsPerTimeFrame | 60 | Requests allowed in one time frame |
BruteForceProtection:TimeFrameDuration | 00:01:00 | Length of the time frame |
BruteForceProtection:RequestsBlockDuration | 00:15:00 | How long an IP that went over the limit is blocked |
BruteForceProtection:ExcludedPaths | See below | Path prefixes that are never counted or blocked |
Whitelist | 127.0.0.1, ::1 | IPs that are never rate limited, so local clients are exempt |
AuthenticationProtection:MaxFailedAttempts | 5 | Failed logins before the IP is blocked from logging in |
AuthenticationProtection:LoginBlockDuration | 00:05:00 | How long login stays blocked |
The default excluded paths are the floating license routes (/v1/license/request, /v1/license/release, /v1/license/heartbeat and their gRPC counterparts) and login (/v1/auth/login and the gRPC Authenticate). An application that went over the limit elsewhere can still hold and release its floating seats.
A blocked client receives 429 Too Many Requests with a Retry-After header in seconds and the text body Too many requests. Retry after N seconds. Wait at least that long before retrying. A blacklisted IP (Blacklist) always receives 403.
Login protection is separate. After MaxFailedAttempts failed logins, further logins from that IP fail until the block expires, even with the right password. The update routes count unknown, revoked or expired user keys as failed attempts too, and enough of them block logins from that IP in the same way. See IP Filtering for the configuration reference.
Webhooks
The service can call your endpoints when licenses, orders and reports change. Subscriptions are managed with the Webhooks API and require an API key of an Administrator or LicenseManager. For the event types and payloads, see Supported Events and Webhook Payload Reference. Every delivery is signed with HMAC-SHA256 in the X-Babel-Signature header; see Securing Webhooks for how to verify it.
API Sections
Login, logout, the signed-in user and the session of a credential
Activation, floating licenses, validation and license information
Customers, products, orders, licenses, templates, users, API keys and settings
Releases, release assemblies and templates, and downloadable artifacts
Reports sent by applications
The public update feed: update check, feed files, downloads and signing keys
Webhook subscriptions, delivery events and event types
Request and response models