MetricSign
NL|ENStart free →
Best Practices9 min·

Azure Functions als Fabric REST API middleware: authenticatie, polling en de fouten waar niemand je voor waarschuwt

Microsoft Fabric biedt een volwaardige REST API voor job scheduling, item management en workspace automation. Azure Functions is de voor de hand liggende tussenlaag. Maar token-acquisitie, polling van langlopende operaties en timeouts van het consumption plan creëren failure modes die pas in productie opduiken.

Read this article in English →

Fabric heeft een REST API — je pipelines gebruiken die waarschijnlijk nog niet

De meeste teams die programmatisch met Microsoft Fabric werken, klikken nog steeds door de portal of gebruiken de Fabric notebooks UI. Dat wordt een probleem zodra je notebook runs wilt triggeren vanuit een externe scheduler, items wilt aanmaken in workspaces als onderdeel van een CI/CD-flow of vastgelopen jobs wilt annuleren zonder in te loggen via de browser.

De Fabric REST API (api.fabric.microsoft.com/v1) biedt operaties die de volledige levenscyclus bestrijken: het aanmaken en verwijderen van items, het triggeren van on-demand job runs via het Job Scheduler endpoint, het beheren van schedules (tot 20 per item), het opvragen van job instances en het bulk-importeren van item definities. De API volgt Azure conventies: bearer token authenticatie, JSON payloads en langlopende operaties die 202 Accepted teruggeven met een Location header die je pollt.

Azure Functions past van nature als lichtgewicht middleware voor deze API. Een HTTP-triggered function kan een webhook van Azure DevOps ontvangen, een timer-triggered function kan 's nachts Fabric notebook runs starten en een Event Grid-triggered function kan reageren op storage events door downstream Fabric artefacten te verversen. Het patroon is eenvoudig: token ophalen, endpoint aanroepen, response afhandelen.

Maar op drie punten gaat dat mis. Token-acquisitie vereist de juiste scope en een service principal die Fabric herkent. Langlopende operaties vereisen polling logic die Retry-After headers respecteert. En consumption plan functions hebben een standaard timeout van 5 minuten, wat korter is dan veel Fabric operaties nodig hebben. Elk van deze punten verdient een eigen sectie, omdat ze elk een andere klasse van silent failures produceren.

Service principal tokens falen stilletjes als de audience verkeerd is

De Fabric REST API vereist een OAuth 2.0 bearer token met een specifieke audience. De scope die je opvraagt bij Azure AD (nu Entra ID) moet https://api.fabric.microsoft.com/.default zijn. Niet de Power BI scope (https://analysis.windows.net/powerbi/api/.default), niet de Azure management scope (https://management.azure.com/.default). Elk van die scopes geeft een geldige JWT terug, en Fabric weigert die met een 401 of, erger, een 403 die je niets nuttigs vertelt.

In je Azure Function ziet token-acquisitie met Azure.Identity er zo uit:

var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
var token = await credential.GetTokenAsync(
    new TokenRequestContext(new[] { "https://api.fabric.microsoft.com/.default" }));

Voor Python functions met azure-identity:

from azure.identity import ClientSecretCredential
credential = ClientSecretCredential(tenant_id, client_id, client_secret)
token = credential.get_token("https://api.fabric.microsoft.com/.default")

Voordat dit werkt, moet de service principal toegang krijgen in de Fabric admin portal. Navigeer naar Admin Portal → Tenant settings → Developer settings → Service principals can use Fabric APIs. Zet dit aan en beperk het tot een security group die de service principal van je app registration bevat. Sla je deze stap over, dan geeft elke API-aanroep een 401 terug, zonder enige aanwijzing dat het een tenant configuratieprobleem is en geen credential probleem.

De app registration heeft ook de Fabric Service API permission (Application type) nodig in Entra ID → App registrations → API permissions. Zonder die permission wordt het token wel uitgegeven maar mist het de claims die Fabric verwacht. De 403 response body zegt InsufficientPrivileges, wat je tenminste in de goede richting wijst. Sla client secrets op in Azure Key Vault en verwijs ernaar via de Key Vault references van de function app in application settings. Hardgecodeerde secrets in code of in local.settings.json die in een repo belanden zijn een veelgemaakte shortcut die uitloopt op een security incident.

Azure Function naar Fabric REST API: aanvraag lifecycle Azure Function getriggerd (timer/HTTP/event) Service principal token ophalen met Fabric scope POST naar Fabric REST API endpoint Response 200? Resultaat teruggeven Response 202? Location header ophalen Poll Get Operation State met Retry-After delay State = Succeeded: Get Operation Result State = Failed: log foutobject en alert sturen Response 401/403: scope en tenant settings Response 429: Exponential backoff met jitter
Azure Function naar Fabric REST API: aanvraag lifecycle

Langlopende operaties vereisen expliciete polling — 202 is geen 'klaar'

Meerdere Fabric REST API-operaties geven HTTP 202 Accepted terug in plaats van een direct resultaat. Het triggeren van een on-demand job via POST /v1/workspaces/{workspaceId}/items/{itemId}/jobs/instances?jobType=RunNotebook is er één. Bulk export en bulk import van item definities zijn er andere. De 202 response bevat een Location header die verwijst naar het operatiestatus-endpoint en vaak een Retry-After header die aangeeft hoeveel seconden je moet wachten voordat je pollt.

Een naïeve Azure Function implementatie vuurt de POST af, ontvangt 202 en geeft succes terug aan de aanroeper. De job kan 30 seconden later gefaald hebben, maar niemand controleert dit. Correcte polling logic vereist een loop:

var response = await httpClient.PostAsync(jobUrl, content);
if (response.StatusCode == HttpStatusCode.Accepted)
{
    var operationUrl = response.Headers.Location.ToString();
    var retryAfter = response.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(10);
    
    while (true)
    {
        await Task.Delay(retryAfter);
        var statusResponse = await httpClient.GetAsync(operationUrl);
        var status = JsonDocument.Parse(await statusResponse.Content.ReadAsStringAsync());
        var state = status.RootElement.GetProperty("status").GetString();
        
        if (state == "Succeeded") break;
        if (state == "Failed")
            throw new Exception(status.RootElement.GetProperty("error").ToString());
    }
}

Het Get Operation State endpoint geeft een status-veld terug met waarden als NotStarted, Running, Succeeded en Failed. Het Get Operation Result endpoint geeft de daadwerkelijke output zodra de operatie voltooid is. Het onderscheid tussen state en result negeren is een veelgemaakte fout: het result endpoint pollen voordat de operatie klaar is, geeft een 200 terug met een leeg of gedeeltelijk resultaat, niet een fout.

Retry-After respecteren is meer dan beleefdheid. Fabric past rate limiting toe (HTTP 429) op tenant niveau. Agressiever pollen dan de header aangeeft vergroot de kans op throttling, wat vervolgens niet alleen je eigen function vertraagt maar alle andere API consumers in de tenant. Bij een 429 bevat de response zijn eigen Retry-After header: houd die aan of gebruik exponential backoff.

Consumption plan timeouts stoppen je polling loop na 5 minuten

Azure Functions op het Consumption plan hebben een standaard uitvoeringstimeout van 5 minuten. De maximaal configureerbare timeout op Consumption is 10 minuten (zet functionTimeout in host.json op 00:10:00). Een Fabric notebook run die 12 minuten duurt, overleeft je function elke keer.

Dit is het meest voorkomende production failure patroon voor deze architectuur. De function triggert de Fabric job, begint met pollen en wordt door de host afgebroken voordat de job klaar is. De function logs tonen geen fout, alleen een abrupt einde. De Fabric job zelf kan slagen of falen, maar de function legt de uitkomst nooit vast.

Drie benaderingen lossen dit op. De eerste is een Durable Functions orchestration gebruiken in plaats van een enkele function. Durable Functions gebruiken checkpointing, zodat de orchestrator 30 seconden kan awaiten op een timer, pollen en herhalen, zonder compute te verbruiken tijdens het wachten. De totale orchestration kan dagen draaien als dat nodig is:

[FunctionName("PollFabricJob")]
public static async Task RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var operationUrl = context.GetInput<string>();
    while (true)
    {
        var status = await context.CallActivityAsync<string>("CheckStatus", operationUrl);
        if (status == "Succeeded" || status == "Failed") return;
        await context.CreateTimer(
            context.CurrentUtcDateTime.AddSeconds(30), CancellationToken.None);
    }
}

De tweede optie is overstappen naar een Premium of Dedicated (App Service) plan, waar de timeout respectievelijk 30 minuten of onbeperkt is. Dit kost meer maar elimineert de architectuurcomplexiteit van Durable Functions.

De derde optie, het patroon dat het beste schaalt, is triggering loskoppelen van monitoring. Laat de Azure Function de Fabric job triggeren en direct de operation URL teruggeven. Een afzonderlijk monitoring systeem controleert of de job binnen het verwachte tijdvenster is voltooid. Als dat niet zo is, wil je dat weten. Dit is waar MetricSign concrete waarde levert: het monitort Fabric pipeline uitvoering end-to-end en geeft een alert bij runs die de verwachte duur overschrijden of stilletjes falen, met root cause context inclusief het specifieke job instance ID en de foutmelding, details die je function nooit heeft vastgelegd omdat het al was uitgevallen.

Foutresponses van Fabric volgen een patroon — leer het één keer

Fabric REST API fouten geven een consistente JSON structuur terug met een error object dat code en message velden bevat. Sommige bevatten een innerError met aanvullende details. De meest voorkomende codes bij aanroepen vanuit Azure Functions:

ItemNotFound (404) betekent meestal dat de workspace ID of item ID verkeerd is, of dat de service principal geen toegang heeft tot die specifieke workspace. Fabric maakt geen onderscheid tussen 'bestaat niet' en 'je hebt geen toegang': beide geven een 404 terug. Dit is een beveiligingsmaatregel maar maakt debuggen lastiger. Controleer of de service principal werkruimtelid is met minimaal de Contributor rol.

Unauthorized (401) is bijna altijd te herleiden naar het scope-probleem dat eerder beschreven is, een verlopen token of de tenant setting die service principal toegang niet inschakelt. Als je function op een timer schedule draait, kan token caching 401's veroorzaken wanneer het gecachede token verloopt. Gebruik GetTokenAsync met forceRefresh of laat de Azure.Identity bibliotheek de token lifecycle beheren. Sla tokens niet handmatig op.

TooManyRequests (429) is Fabric's rate limit response. De API handhaaft throttling per tenant. Als meerdere Azure Functions, Power BI refreshes en handmatige API-aanroepen tegelijkertijd dezelfde tenant raken, cascaderen 429's. Implementeer exponential backoff met jitter, niet vaste retry-intervallen. De waarde in de Retry-After header is in seconden.

InvalidItemType (400) treedt op als je een jobType parameter doorgeeft die niet overeenkomt met het item type. jobType=RunNotebook uitvoeren op een Lakehouse item, bijvoorbeeld. Het Job Scheduler endpoint valideert of het job type compatibel is met het target item.

Log elke API response, inclusief status code, headers en body, in Application Insights. Als een Fabric operatie om 02:00 uur 's nachts mislukt, is het verschil tussen 'er is iets misgelopen' en 'POST naar /jobs/instances gaf 429 terug met Retry-After: 60 op 02:14:33 UTC' het verschil tussen een fix van 5 minuten en een uur debuggen.

Alles samenbrengen: een timer-triggered function die elke nacht een Fabric notebook uitvoert

Hier is het praktische end-to-end patroon. Een timer-triggered Azure Function haalt een service principal token op, triggert een Fabric notebook run via de Job Scheduler API en logt de operation URL voor externe monitoring.

[FunctionName("NightlyFabricNotebook")]
public static async Task Run(
    [TimerTrigger("0 0 2 * * *")] TimerInfo timer,
    ILogger log)
{
    var credential = new ClientSecretCredential(
        Environment.GetEnvironmentVariable("FABRIC_TENANT_ID"),
        Environment.GetEnvironmentVariable("FABRIC_CLIENT_ID"),
        Environment.GetEnvironmentVariable("FABRIC_CLIENT_SECRET"));
    
    var token = await credential.GetTokenAsync(
        new TokenRequestContext(new[] { "https://api.fabric.microsoft.com/.default" }));
    
    var client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = 
        new AuthenticationHeaderValue("Bearer", token.Token);
    
    var workspaceId = Environment.GetEnvironmentVariable("FABRIC_WORKSPACE_ID");
    var itemId = Environment.GetEnvironmentVariable("FABRIC_NOTEBOOK_ID");
    var url = $"https://api.fabric.microsoft.com/v1/workspaces/{workspaceId}" +
             $"/items/{itemId}/jobs/instances?jobType=RunNotebook";
    
    var response = await client.PostAsync(url, null);
    
    log.LogInformation("Fabric job trigger: {StatusCode}", response.StatusCode);
    log.LogInformation("Operation URL: {Location}", 
        response.Headers.Location?.ToString() ?? "none");
    
    if (response.StatusCode != HttpStatusCode.Accepted)
    {
        var body = await response.Content.ReadAsStringAsync();
        log.LogError("Fabric API error: {Body}", body);
    }
}

De function triggert om 02:00 UTC. Er wordt niet gepollt: triggeren en loggen. Zo blijft de uitvoering ruim binnen de 5-minuten timeout van het consumption plan. Configuratiewaarden komen uit omgevingsvariabelen die zijn gekoppeld aan Key Vault references: @Microsoft.KeyVault(VaultName=myvault;SecretName=fabric-client-secret) in de application settings van de function app.

Let op de CRON expressie: Azure Functions gebruikt NCrontab met zes velden (seconde minuut uur dag maand dag-van-week), niet het vijf-velden formaat van Unix cron. 0 2 schrijven in plaats van 0 0 2 triggert op seconde 0, minuut 2, dus elk uur in plaats van eenmaal per nacht. Dit is een veelgemaakte fout die tientallen onverwachte Fabric job runs genereert voordat iemand het opmerkt.

Veelgestelde vragen

Kan ik een managed identity gebruiken in plaats van een service principal client secret voor Fabric REST API-aanroepen?+
Ja. Azure Functions ondersteunt system-assigned en user-assigned managed identities. Gebruik `DefaultAzureCredential` of `ManagedIdentityCredential` uit de `Azure.Identity` bibliotheek met dezelfde Fabric scope (`https://api.fabric.microsoft.com/.default`). De enterprise application van de managed identity moet nog steeds worden toegevoegd aan een security group die is ingeschakeld in de Fabric tenant settings voor service principal API-toegang. Managed identity elimineert secret rotatie maar vereist dat de function app en de Fabric tenant in dezelfde Entra ID tenant zitten.
Wat is de rate limit van de Fabric REST API?+
Microsoft past throttling per tenant toe maar publiceert geen vaste limieten. Bij throttling geeft de API HTTP 429 terug met een Retry-After header die aangeeft hoeveel seconden je moet wachten. Throttling is gedeeld over alle API consumers in de tenant: Azure Functions, Power BI refreshes, handmatige REST-aanroepen en Fabric pipelines putten allemaal uit dezelfde pool. Implementeer exponential backoff met jitter in plaats van vaste retry-intervallen om gesynchroniseerde retry storms te voorkomen.
Hoe trigger ik een Fabric pipeline (geen notebook) vanuit een Azure Function?+
Gebruik hetzelfde Job Scheduler endpoint maar verander de jobType parameter. Voor Data Pipelines gebruik je `jobType=Pipeline`. Het endpoint is `POST /v1/workspaces/{workspaceId}/items/{itemId}/jobs/instances?jobType=Pipeline`. De item ID moet verwijzen naar een pipeline item, niet naar een notebook of lakehouse. Als het jobType niet overeenkomt met het item type, geeft de API een 400 `InvalidItemType` fout terug.

Gerelateerde integraties

Gerelateerde artikelen