Print-ready: Letter, margins None, Background graphics on.
OnlyBCFans BC Object
Registry
Business Central

Developer guide · REST API v1

BC Object Registry
API guide

Issue and track object ranges from code

For: Business Central developers and DevOps people who want to issue and track object ranges from Business Central, VS Code, scripts or CI.

Reference: the interactive reference is at /docs/api on your BC Object Registry site. The specs are at /api/v1/openapi.json (OpenAPI 3.0) and /api/v1/swagger.json (OpenAPI 2.0, for Power Platform and Copilot Studio).

In the examples, https://registry.example.com stands for your BC Object Registry address.

Quick start in three calls

1Create an API keyAPI keys → Create API keyShown once: copy it into a secret store.
2Call /meGET/api/v1/meKey in the X-API-Key header.
3Issue a rangePOST/ranges/allocateGet idRanges ready for app.json.
ProductBC Object Registry
PublisherOnlyBCFans
Base path/api/v1
SpecsOpenAPI 3.0 · 2.0
BC Object Registry · API guide1
01

Quick start

Three steps from nothing to an issued range.

1

Create an API key

In the portal

In the portal, open API keys → Create API key. Name it after where it will be used (for example "BC sandbox" or "GitHub Actions"), and choose its scopes. The key is shown once: copy it into a secret store.

2

Call the API with the key in the X-API-Key header

GET /api/v1/me

curl -H "X-API-Key: $BCOR_API_KEY" https://registry.example.com/api/v1/me
3

Issue a range

POST /api/v1/ranges/allocate

curl -H "X-API-Key: $BCOR_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -X POST https://registry.example.com/api/v1/ranges/allocate \
  -d '{"name":"Contoso Labels","size":20,"environments":["Production"]}'

The response has the range, and idRanges ready for app.json:

{
  "range": { "from": 50120, "to": 50139 },
  "rangeText": "50120–50139",
  "idRanges": [{ "from": 50120, "to": 50139 }],
  "app": { "id": "…", "name": "Contoso Labels", "status": "Pending", "environments": ["Production"] }
}

Reference. The interactive reference is at /docs/api on your BC Object Registry site. The specs are at /api/v1/openapi.json (OpenAPI 3.0) and /api/v1/swagger.json (OpenAPI 2.0, for Power Platform and Copilot Studio).

BC Object Registry · API guide2
02

Authentication and scopes

ScopeAllows
readList apps, tenants and pools; free ranges; check a range; /me
allocatePOST /ranges/allocate (issue a new range)
writeRegister, update and delete apps; import; add and update tenants

Members create and revoke their own keys; org admins see and revoke everyone's. Plans allow these active keys:

2active keysFree
25active keysPremium
50active keysPremium (Partner)

Revoking a key takes effect at once. Keys are stored only as a hash; nobody can show you a key again, including us.

03

Tenants: which registry a call uses

Object IDs only collide inside one Business Central tenant, so every PTE app belongs to a tenant.

For partners and everyone else

Name the tenant, or leave it out

Free and Premium

Have one tenant: you can leave the tenant out.

Premium (Partner)

Has a registry per customer tenant: name it with tenantId (our id, from GET /tenants) or bcTenantId (the Microsoft Entra tenant ID). From AL, bcTenantId is AzureADTenant.GetAadTenantId(), so code running inside a customer's Business Central lands in that customer's registry.

AppSource apps

Are partner-wide: send "type": "AppSource" and no tenant.

Environments

Are named by their Business Central names (case-insensitive), e.g. ["Production", "UAT"]. Unknown names are refused with the tenant's list. Ranges are always checked across the whole tenant, whatever the environment.

BC Object Registry · API guide3
04

Endpoints

All paths are under /api/v1. GET POST PATCH DELETE read allocate write

Method and pathScopeWhat it does
GET/mereadThe workspace, plan and scopes this key acts with
Tenants
GET/tenantsreadTenants with their environments and admin center links
GET/tenants/{id}readOne tenant, by our id or its BC Tenant ID
POST/tenantswriteAdd a customer tenant (Premium (Partner))
PATCH/tenants/{id}writeRename, change domain or Tenant ID, activate or deactivate
Apps
GET/appsreadList apps. Filters: tenantId, bcTenantId, type, status, appId, q
GET/apps/{id}readOne app with ranges, environments and overlaps
POST/appswriteRegister an app that already has its range(s)
PATCH/apps/{id}writeChange only the fields sent (e.g. set appId and status: "Active")
DELETE/apps/{id}?confirm=<name>writeDelete an app and free its ranges; restorable in the portal for 30 days
Ranges and pools
POST/ranges/allocateallocateIssue the next free range (first fit, safe under concurrency)
POST/ranges/checkreadIs from–to free? Which apps would it overlap?
GET/poolsreadPools with usage
GET/pools/{id}/freereadA pool's free ranges, live
Import
POST/importwriteBulk import existing apps, with a dry-run preview
GET/import/{id}writeAn import's status and counts
POST/import/{id}/undowriteUndo a committed import within 24 hours
→

The next pages show the request bodies for issuing a range, checking a range, registering an app from CI, and importing.

BC Object Registry · API guide4
04

Issue a range

POST /ranges/allocateallocate

{ "name": "Contoso Labels", "size": 20, "bcTenantId": "…", "environments": ["Production"] }

Check a range

POST /ranges/checkread

{ "from": 50100, "to": 50119 }

Returns free (true/false), the apps it would overlap, and the pool it falls in.

Register an app (CI)

POST /appswrite

{
  "name": "Warehouse Labels",
  "appId": "d1000001-0000-4000-8000-000000000001",
  "ranges": [{ "from": 50100, "to": 50119 }],
  "status": "Active",
  "repoName": "warehouse-labels",
  "environments": ["Production"]
}
i

ranges can also be a string: "50100-50119; 50200-50209". Overlaps are allowed, flagged and returned in warnings. A duplicate name or App ID is a 409.

BC Object Registry · API guide5
04

Import

POST /import takes either apps (a JSON array shaped like POST /apps) or file ({ "name": "registry.json", "content": "…" }). Files can be your GitHub registry's registry.json or ObjectRegistry.md, an app.json, a CSV, or a .zip of repos sent with "encoding": "base64".

1 · Preview

The default, dryRun: true: returns each row as new, duplicate, invalid or over-limit, with overlaps and warnings, plus a previewId. Nothing is written.

2 · Commit

Applies exactly what was previewed. Duplicates are skipped unless decisions says update or new (by row index).

3 · Undo

POST /import/{previewId}/undo within 24 hours.

Commit body

{ "dryRun": false, "previewId": "…", "decisions": { "3": "update" } }
05

Errors

Errors use RFC 9457 problem details, Content-Type: application/problem+json:

{ "type": "about:blank", "title": "Tenant required", "status": 422, "detail": "Name the tenant with tenantId or bcTenantId…", "tenants": [ … ] }
StatusMeaning
400Malformed JSON
401Missing, unknown or revoked key; disabled workspace; blocked key owner
403The key lacks the scope
404Not found (or not in this workspace)
409Conflict: a duplicate name or App ID
413File too large (4 MB)
422Refused: validation, plan limit, inactive tenant, no free block, unknown environment
429Rate limit exceeded

Validation errors include errors: [{ "path": "size", "message": "…" }].

BC Object Registry · API guide6
06

Rate limits

Per key, per minute.

60/ minFree
600/ minPremium
1,200/ minPremium (Partner)
HeaderCarried on
X-RateLimit-LimitEvery response
X-RateLimit-RemainingEvery response
X-RateLimit-ResetEvery response (Unix seconds)
Retry-AfterA 429 (seconds)
07

Idempotency

Send Idempotency-Key: <unique string> (a UUID is ideal) on POST /ranges/allocate, POST /apps and POST /import commits. If a call times out and you retry with the same key, you get the first response back (with Idempotent-Replayed: true) instead of a second range.

1

Send the call

POST /ranges/allocate
Idempotency-Key: K

The call times out.

→
2

Retry with the same key

POST /ranges/allocate
Idempotency-Key: K

Use the same Idempotency-Key.

→
3

First response back

Idempotent-Replayed: true

You get the first response, not a second range.

i

Keys are remembered for 24 hours per workspace.

BC Object Registry · API guide7
08

Samples

The samples are in the repository under docs/api/samples/. Full listings are in the appendix.

ALBusiness Central ObjectRegistryClient.Codeunit.al

Issues a range for the tenant and environment it runs in, and checks ranges.

  • The API key is kept in Isolated Storage.
  • It sends GetAadTenantId() as bcTenantId and the current environment name.
  • It adds an Idempotency-Key.
  • SecretText headers need Business Central 2024 release wave 1 (v24) or later.
var
    Registry: Codeunit "OBCF Object Registry Client";
begin
    Registry.SetApiKey(ApiKeyFromSetupPage);
    Message('Issued %1', Registry.IssueRange('Warehouse Labels', 20));
end;

PowerShellissue-range.ps1

Issues a range and writes idRanges into app.json:

$env:BCOR_API_KEY = "bcor_…"
./issue-range.ps1 -Name "Contoso Labels" -Size 20 -Environments Production

VS Codevscode-tasks.json

Adds Run Task → Issue object range. It prompts for a name and size and runs the PowerShell script on the open project.

GitHub Actionsgithub-actions-register-app.yml

Registers the repository's app.json on every push to main. It uses POST /import, so apps already registered are skipped. Store the key as the BCOR_API_KEY secret (write scope).

curlcurl.sh

Covers /me, listing, issuing, checking and importing a registry.json.

09

Copilot Studio and Power Platform

i

Use the OpenAPI 2.0 file (/api/v1/swagger.json) as a Copilot Studio REST API tool or a Power Platform custom connector. See the Copilot Studio connector guide.

BC Object Registry · API guide8
A

Appendix: sample listings

The real samples from docs/api/samples/, in full.

ALObjectRegistryClient.Codeunit.alBusiness Central client codeunit · lines 1–52

/// <summary>/// Calls the BC Object Registry API from Business Central (AL HttpClient)./// Issues a new object range for this tenant and environment, or checks one./// The API key lives in Isolated Storage (company-independent), never in code./// Pick an object ID from your own range; 50100 is only an example./// </summary>codeunit 50100 "OBCF Object Registry Client"{    Access = Public;     var        BaseUrlTxt: Label 'https://registry.onlybcfans.com/api/v1', Locked = true; // replace with your BC Object Registry address        ApiKeyStorageKeyTok: Label 'OBCF-ObjectRegistry-ApiKey', Locked = true;     procedure SetApiKey(ApiKey: SecretText)    begin        IsolatedStorage.Set(ApiKeyStorageKeyTok, ApiKey, DataScope::Module);    end;     /// <summary>Issue the next free range of Size IDs for ProjectName in this tenant, marked as deployed to this environment.</summary>    /// <returns>The range as text, e.g. "50120–50139".</returns>    procedure IssueRange(ProjectName: Text; Size: Integer): Text    var        AzureADTenant: Codeunit "Azure AD Tenant";        EnvironmentInformation: Codeunit "Environment Information";        Body: JsonObject;        Environments: JsonArray;        Response: JsonObject;        Token: JsonToken;    begin        Body.Add('name', ProjectName);        Body.Add('size', Size);        // Lands in the registry for this customer's tenant (Premium (Partner) has one per tenant).        Body.Add('bcTenantId', AzureADTenant.GetAadTenantId());        Environments.Add(EnvironmentInformation.GetEnvironmentName());        Body.Add('environments', Environments);         Response := Send('POST', '/ranges/allocate', Body, CreateGuid());        Response.Get('rangeText', Token);        exit(Token.AsValue().AsText());    end;     /// <summary>True when FromId..ToId is free in this tenant's registry.</summary>    procedure IsRangeFree(FromId: Integer; ToId: Integer): Boolean    var        AzureADTenant: Codeunit "Azure AD Tenant";        Body: JsonObject;        Response: JsonObject;        Token: JsonToken;    begin        Body.Add('from', FromId);        Body.Add('to', ToId);

Continued on the next page.

BC Object Registry · API guide9
A

ALObjectRegistryClient.Codeunit.alcontinued · lines 53–95

        Body.Add('bcTenantId', AzureADTenant.GetAadTenantId());        Response := Send('POST', '/ranges/check', Body, CreateGuid());        Response.Get('free', Token);        exit(Token.AsValue().AsBoolean());    end;     local procedure Send(Method: Text; Path: Text; Body: JsonObject; IdempotencyKey: Guid) Result: JsonObject    var        Client: HttpClient;        Request: HttpRequestMessage;        Response: HttpResponseMessage;        Content: HttpContent;        Headers: HttpHeaders;        ApiKey: SecretText;        BodyText: Text;        ResponseText: Text;        ProblemErr: Label 'BC Object Registry returned %1: %2', Comment = '%1 = HTTP status, %2 = problem detail';        NoKeyErr: Label 'Set the BC Object Registry API key first (SetApiKey).';    begin        if not IsolatedStorage.Get(ApiKeyStorageKeyTok, DataScope::Module, ApiKey) then            Error(NoKeyErr);         Body.WriteTo(BodyText);        Content.WriteFrom(BodyText);        Content.GetHeaders(Headers);        Headers.Remove('Content-Type');        Headers.Add('Content-Type', 'application/json');         Request.Method := Method;        Request.SetRequestUri(BaseUrlTxt + Path);        Request.Content := Content;        Request.GetHeaders(Headers);        Headers.Add('X-API-Key', ApiKey);        // A retry with the same key returns the same range instead of issuing a second one.        Headers.Add('Idempotency-Key', Format(IdempotencyKey, 0, 4));         Client.Send(Request, Response);        Response.Content.ReadAs(ResponseText);        if not Response.IsSuccessStatusCode() then            Error(ProblemErr, Response.HttpStatusCode(), ResponseText);        Result.ReadFrom(ResponseText);    end;}
OnlyBCFans BC Object Registry

Questions or problems? Email info@onlybcfans.com.

BC Object Registry by OnlyBCFans. Not affiliated with Microsoft. Microsoft, Dynamics 365 and Business Central are trademarks of the Microsoft group of companies.

BC Object Registry · API guide10
A

PowerShellissue-range.ps1Issue a range and write idRanges into app.json

# Issue a new object range with PowerShell 7 and write it into app.json.# Usage: ./issue-range.ps1 -Name "Contoso Labels" -Size 20 [-BcTenantId <guid>] [-Environments Production,UAT]param(  [Parameter(Mandatory)] [string] $Name,  [Parameter(Mandatory)] [int] $Size,  [string] $BcTenantId,  [string[]] $Environments = @(),  [string] $AppJson = "./app.json",  [string] $BaseUrl = $env:BCOR_URL ?? "https://registry.onlybcfans.com")$key = $env:BCOR_API_KEYif (-not $key) { throw "Set BCOR_API_KEY to an API key from the portal (API keys)." } $body = @{ name = $Name; size = $Size; environments = $Environments }if ($BcTenantId) { $body.bcTenantId = $BcTenantId } $result = Invoke-RestMethod -Method Post -Uri "$BaseUrl/api/v1/ranges/allocate" `  -Headers @{ "X-API-Key" = $key; "Idempotency-Key" = [guid]::NewGuid().ToString() } `  -ContentType "application/json" -Body ($body | ConvertTo-Json) Write-Host "Issued $($result.rangeText) for $Name"if (Test-Path $AppJson) {  $app = Get-Content $AppJson -Raw | ConvertFrom-Json  $app | Add-Member -NotePropertyName idRanges -NotePropertyValue $result.idRanges -Force  $app | ConvertTo-Json -Depth 20 | Set-Content $AppJson  Write-Host "Updated idRanges in $AppJson"}

VS Codevscode-tasks.jsonRun Task → Issue object range

// .vscode/tasks.json: "Run Task → Issue object range" asks for a name and size, then writes idRanges into app.json.// Needs PowerShell 7 and issue-range.ps1 next to app.json, with BCOR_API_KEY set in your environment.{  "version": "2.0.0",  "inputs": [    { "id": "name", "type": "promptString", "description": "Project name" },    { "id": "size", "type": "promptString", "description": "How many object IDs?", "default": "20" }  ],  "tasks": [    {      "label": "Issue object range",      "type": "shell",      "command": "pwsh",      "args": ["-File", "${workspaceFolder}/issue-range.ps1", "-Name", "${input:name}", "-Size", "${input:size}", "-AppJson", "${workspaceFolder}/app.json"],      "problemMatcher": []    }  ]}
BC Object Registry · API guide11
A

curlcurl.sh/me, listing, issuing, checking, importing

#!/usr/bin/env bash# curl examples. Set BCOR_URL (e.g. https://registry.onlybcfans.com) and BCOR_API_KEY first.set -euo pipefailH=(-H "X-API-Key: ${BCOR_API_KEY}" -H "Content-Type: application/json") # Who am I (workspace, plan, scopes)curl -s "${H[@]}" "$BCOR_URL/api/v1/me" # List active PTE apps in one tenant (by BC Tenant ID)curl -s "${H[@]}" "$BCOR_URL/api/v1/apps?type=PTE&status=Active&bcTenantId=00000000-0000-0000-0000-000000000000" # Issue 20 IDs, marked as deployed to Production; safe to retry with the same Idempotency-Keycurl -s "${H[@]}" -H "Idempotency-Key: $(uuidgen)" -X POST "$BCOR_URL/api/v1/ranges/allocate" \  -d '{"name":"Contoso Labels","size":20,"environments":["Production"]}' # Is 50100–50119 free?curl -s "${H[@]}" -X POST "$BCOR_URL/api/v1/ranges/check" -d '{"from":50100,"to":50119}' # Import your GitHub registry.json (preview first, then commit with the previewId)curl -s "${H[@]}" -X POST "$BCOR_URL/api/v1/import" \  -d "$(jq -n --arg c "$(cat registry.json)" '{dryRun:true, file:{name:"registry.json", content:$c}}')"

GitHub Actionsgithub-actions-register-app.ymlRegister app.json on push to main

# .github/workflows/register-app.yml# On every push to main, register (or update) this repo's app.json in BC Object Registry.# Secrets: BCOR_API_KEY (write scope). Variables: BCOR_URL, BCOR_BC_TENANT_ID (Partner workspaces).name: Register app in BC Object Registryon:  push:    branches: [main]    paths: [app.json]jobs:  register:    runs-on: ubuntu-latest    steps:      - uses: actions/checkout@v4      - name: Register or update the app        env:          BCOR_URL: ${{ vars.BCOR_URL }}          BCOR_API_KEY: ${{ secrets.BCOR_API_KEY }}          BCOR_BC_TENANT_ID: ${{ vars.BCOR_BC_TENANT_ID }}        run: |          set -euo pipefail          content=$(jq -c '.' app.json)          body=$(jq -n --arg c "$content" --arg t "${BCOR_BC_TENANT_ID:-}" --arg repo "${GITHUB_REPOSITORY#*/}" \            '{dryRun:false, file:{name:"app.json", content:$c}} + (if $t != "" then {bcTenantId:$t} else {} end)')          # Import keeps existing apps (duplicates are skipped by default); new apps are registered.          curl -sf -H "X-API-Key: $BCOR_API_KEY" -H "Content-Type: application/json" -H "Idempotency-Key: $GITHUB_SHA" \            -X POST "$BCOR_URL/api/v1/import" -d "$body" | jq '.counts, .committed'
BC Object Registry · API guide12