Skip to Content
New release 12 available 🎉
API ReferenceOverview

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:SerializeJSonEnumAsString is enabled.
  • Properties with a null value are left out of responses while Application:IgnoreJSonDefaultValues is true, as it is in the default configuration.
  • A client that sends Accept: application/toon receives TOON instead of JSON. Without that header the response is JSON.
  • Uploading a release artifact uses multipart/form-data, and the update feed files are text/yaml. See Product Releases and Updates.

Authentication

The API accepts two credentials. Which one an endpoint takes is shown under each endpoint as Authentication.

CredentialHeaderObtained fromUsed by
API keyx-api-key: <key>The web application, Babel Desktop, or POST /v1/api-keysManagement API, product releases, release downloads, webhooks; also licensing operations and reports
Bearer token (JWT)Authorization: Bearer <token>POST /v1/auth/loginSigned-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:

PermissionValueAllows
Read1GET
Write2PUT, PATCH
Delete4DELETE
Create8POST
All15every 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>.

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.

AreaRoutesCredentialRoles
Update feed and feed keys/v1/updates/..., /v1/extensions/keysNoneAnyone
LoginPOST /v1/auth/loginNoneAnyone
Signed-in user/v1/auth/user, POST /v1/auth/logoutBearerAny user
SessionGET /v1/auth/sessionBearer or API keyAny
Licensing operations/v1/license/...Bearer or API keyNo role required
Reports sent by applicationsPOST /v1/reportBearer or API keyNo role required
Customers, contacts, products, orders, resources/v1/customers, /v1/contacts, /v1/products, /v1/orders, /v1/resourcesAPI keyAdministrator, LicenseManager, Sales
Licenses (list, create, update) and license tokens/v1/licenses, /v1/license-tokensAPI keyAdministrator, LicenseManager, Sales
License templates (list, template licenses)GET /v1/license-templates...API keyAdministrator, LicenseManager, Sales
Product releases, release assemblies and release license templates/v1/products/{productId}/releases...API keyAdministrator, LicenseManager, Sales
Log, server info, email/v1/log, /v1/info, /v1/email/...API keyAdministrator, LicenseManager, Sales
License deletionDELETE /v1/licenses/{licenseId}API keyAdministrator, LicenseManager
License templates (create, update, delete)POST, PUT, DELETE /v1/license-templatesAPI keyAdministrator, LicenseManager
License traces and received reports/v1/license-traces, /v1/reportsAPI keyAdministrator, LicenseManager
Release downloads/v1/products/{productId}/releases/{releaseId}/downloads...API keyAdministrator, LicenseManager
Webhooks/v1/webhooks/...API keyAdministrator, LicenseManager
Users, user settings, roles, API keys/v1/users, /v1/roles, /v1/api-keysAPI keyAdministrator
Assemblies and service settings/v1/assemblies, /v1/settingsAPI keyAdministrator

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 the X-Babel-User-Key header or the userKey query parameter. See Updates.
  • GET /v1/extensions/keys, the public keys that sign the feed.
  • The health check at /health (Application:HealthCheckEndPoint), when Application:EnableHealthCheck is true. 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:

ParameterMeaningExample
filterA Dynamic LINQ  expression on the entity propertiesuserKey="AAAAA-BBBBB-CCCCC-DDDDD"
sortComma-separated properties; prefix - for descending, + or nothing for ascending-createdAt,name
skipNumber of items to skip20
takeMaximum number of items to return10
selectComma-separated properties to returnid,code,name
includeComma-separated related entities to load, where the route supports itContacts

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:

  • 400 for a request the client can correct, such as a missing or invalid field. Many “not found” cases on the Management API also answer 400, with the reason in message.
  • 401 for a failed login (Invalid username or password., Invalid user key.).
  • 500 for anything else, including a failed database operation.
  • 520 plus the gRPC status code for the licensing errors shared with the gRPC services. The body message names the error. See Error Codes.
HTTP statusgRPC statusExample message
523InvalidArgumentInvalid license key; The machine code is required for license activation
525NotFoundLicense key not found; License token not found
526AlreadyExistsThe license has already been activated
527PermissionDeniedThe license has been revoked; The license has expired
528ResourceExhaustedThe maximum number of concurrent users for the license has been reached
529FailedPreconditionThe license has not been activated
535DataLossThe 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

StatusMeaning
400Invalid request, or an entity referenced by the request does not exist
401No 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
403Valid credential without the required role or API key permission; on update routes, a refused license or download
404Unknown route; on update routes, an unknown product or artifact; on webhook routes, an unknown subscription or event
429Too many requests from the client IP; see Rate Limiting
500Unexpected error, invalid filter expression, or a login from an IP blocked after failed attempts
52xLicensing error mapped from a gRPC status (table above)

Rate Limiting

The IpFiltering section of appsettings.json protects the service. The defaults are:

SettingDefaultEffect
BruteForceProtection:EnabledtrueCounts requests per client IP
BruteForceProtection:MaxRequestsPerTimeFrame60Requests allowed in one time frame
BruteForceProtection:TimeFrameDuration00:01:00Length of the time frame
BruteForceProtection:RequestsBlockDuration00:15:00How long an IP that went over the limit is blocked
BruteForceProtection:ExcludedPathsSee belowPath prefixes that are never counted or blocked
Whitelist127.0.0.1, ::1IPs that are never rate limited, so local clients are exempt
AuthenticationProtection:MaxFailedAttempts5Failed logins before the IP is blocked from logging in
AuthenticationProtection:LoginBlockDuration00:05:00How 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

Last updated on