> For the complete documentation index, see [llms.txt](https://docs.stoik.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.stoik.io/help/help-nl/email-security/microsoft-365/purview-script.md).

# Purview-script

De geautomatiseerde versie van [Purview-activeringsfouten](/help/help-nl/email-security/microsoft-365/purview-errors.md). Het maakt verbinding met Exchange Online, dwingt een uitschakeling en daarna een herinschakeling van de opname van auditlogboeken af, en bevestigt het resultaat. Als de tenant niet-gehydrateerd is, voert het `Enable-OrganizationCustomization`, wacht tot het klaar is en schakelt vervolgens het auditloggen opnieuw in. Als activering echt wordt geweigerd, wordt een diagnose afgedrukt (accountrollen, tenant-hydratatie, licentie- en abonnementsstatus).

## Vereisten

* **Windows PowerShell** of **Azure Cloud Shell** (PowerShell-modus). Het script weigert te draaien op macOS/Linux, waar de Exchange Online-module faalt bij schrijfbewerkingen.
* Een tenantbeheerdersaccount.

{% hint style="info" %}
Geen Windows-machine? Als de tenant een actief Azure-abonnement heeft, voer het dan uit vanuit [Azure Cloud Shell](https://shell.azure.com) in PowerShell-modus: upload het script en voer het uit zonder `-AdminUpn`. Cloud Shell hergebruikt je sessie, dus er verschijnt geen aanmeldprompt. Purview zelf vereist geen Azure-abonnement.
{% endhint %}

## Voer het script uit

{% stepper %}
{% step %}

### Kopieer het script

Kopieer de [script](#script) met de kopieerknop rechtsboven in het codeblok en sla het op als `Repair-PurviewAuditLogging.ps1`.
{% endstep %}

{% step %}

### Sta scriptuitvoering toe

Voer dit in PowerShell eenmaal per venster uit (alleen voor deze sessie, niets blijvends):

```powershell
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
```

{% endstep %}

{% step %}

### Voer het uit met het beheerdersaccount

Vervang `<admin@clientdomain.com>` met een tenantbeheerder. Er wordt een browser-aanmeldvenster geopend; het script doet de rest.

```powershell
cd C:\path\to\the\folder
.\Repair-PurviewAuditLogging.ps1 -AdminUpn <admin@clientdomain.com>
```

{% endstep %}

{% step %}

### Later verifiëren

`False` direct na het uitvoeren is **normaal**: Microsoft past het binnen enkele minuten toe (tot ongeveer 60 min). Maak opnieuw verbinding om te controleren:

```powershell
Connect-ExchangeOnline -UserPrincipalName <admin@clientdomain.com> -ShowBanner:$false
Get-AdminAuditLogConfig | Format-List UnifiedAuditLogIngestionEnabled
```

`True` betekent dat het klaar is.
{% endstep %}
{% endstepper %}

## Problemen oplossen

Het script voert een diagnose uit en vertelt je in welk geval je zit. Stuur het `.log` bestand dat het heeft gemaakt naar <protect@stoik.io>.

* **Niet-gehydrateerde tenant**: automatisch afgehandeld. Het script voert `Enable-OrganizationCustomization`, wacht op hydratatie en schakelt daarna het auditloggen opnieuw in.
* **403 Verboden / geweigerd**: een echte weigering. Het account mist de juiste rol (de **Audit Logs** rol, toegekend via Organization Management of Compliance Management), of de tenant heeft geen licentie die Exchange Online bevat.
* **Serverfout**: een tijdelijke "probeer het later opnieuw" van Microsoft. Het script probeert het al opnieuw; als het aanhoudt, voer het dan een paar minuten later opnieuw uit.

## Script

{% code title="Repair-PurviewAuditLogging.ps1" lineNumbers="true" %}

```powershell
<#
.SYNOPSIS
    Herstel Microsoft Purview unified audit logging door een uitschakeling en daarna herinschakeling af te dwingen.

.DESCRIPTION
    VEREIST: voer dit uit in Windows PowerShell (5.1 of 7+) OF in Azure Cloud Shell.
    Het script weigert ergens anders te draaien (macOS / desktop Linux) - die omgevingen
    lopen tegen eigenaardigheden van de Exchange Online-module aan en worden niet ondersteund.

      - Windows      -> geef -AdminUpn door (of laat het weg voor een interactieve aanmelding).
      - Cloud Shell  -> aanmelden gebeurt automatisch (de EXO-module hergebruikt je Cloud Shell-identiteit).
                        Gebruik -Device alleen als je expliciet aanmelding met apparaatcode wilt.

    Zelfs als auditlogging al als ingeschakeld wordt weergegeven, kan het Purview-portaal vast blijven hangen op
    de fout. Door eerst uit te schakelen en daarna opnieuw in te schakelen, wordt het ontgrendeld.

    Herinschakelen gebeurt asynchroon: Microsoft kan tot ongeveer 60 minuten nodig hebben om dit te tonen, dus een
    verse uitlezing kan direct na een geslaagde aanroep nog steeds False tonen. Dat is normaal.

    Als inschakelen echt wordt geweigerd, voert het script een diagnose uit: verbonden account &
    rollen, tenant-hydratatiestatus, tenantleeftijd en abonnements-/licentiestatus (de laatste twee
    via Microsoft Graph wanneer beschikbaar) - om de echte oorzaak aan te wijzen (machtigingen, licenties,
    of een te nieuwe tenant).

    Er wordt een .log-bestand met tijdstempel geschreven in de huidige map.

.PARAMETER AdminUpn
    Admin-UPN (bijv. admin@clientdomain.com). Optioneel op Windows (interactieve aanmelding als het wordt weggelaten).

.PARAMETER Device
    Forceer aanmelding met apparaatcode. Optioneel - Cloud Shell meldt automatisch aan zonder dit.

.EXAMPLE
    .\Repair-PurviewAuditLogging.ps1 -AdminUpn admin@clientdomain.com
#>

[CmdletBinding()]
param(
    [string]$AdminUpn,
    [switch]$Device
)

$ErrorActionPreference = 'Stop'
$SupportEmail = 'protect@stoik.io'

# Geduld voor de tijdelijke "probeer het later opnieuw"-fouten van Microsoft.
$RetryCount   = 8
$RetryWaitSec = 30   # 8 x 30s = tot ~4 min per schakeling

# --- Consolehulpen --------------------------------------------------------
function Write-Step { param($m) Write-Host "`n==> $m" -ForegroundColor Cyan }
function Write-Ok   { param($m) Write-Host "    [OK]   $m" -ForegroundColor Green }
function Write-Info { param($m) Write-Host "    [..]   $m" -ForegroundColor Gray }
function Write-Warn2{ param($m) Write-Host "    [WARN] $m" -ForegroundColor Yellow }
function Write-Err  { param($m) Write-Host "    [FAIL] $m" -ForegroundColor Red }

# --- Omgevingsdetectie + voorwaardecontrole ------------------------------
# Cloud Shell zet een van deze; Windows PowerShell 5.1 heeft geen $IsWindows, dus we accepteren ook
# de Desktop-editie.
$IsCloudShell = [bool]$env:ACC_CLOUD `
                -or ($env:POWERSHELL_DISTRIBUTION_CHANNEL -like '*CloudShell*') `
                -or ($env:AZUREPS_HOST_ENVIRONMENT -like '*cloud-shell*')
$OnWindows    = $IsWindows -or ($PSVersionTable.PSEdition -eq 'Desktop')

$exoVersion = (Get-Module -ListAvailable -Name ExchangeOnlineManagement |
               Sort-Object Version -Descending | Select-Object -First 1).Version

Write-Step "Omgeving"
Write-Host "    Besturingssysteem / platform : $([System.Environment]::OSVersion.VersionString)"
Write-Host "    PowerShell    : $($PSVersionTable.PSVersion) ($($PSVersionTable.PSEdition))"
Write-Host "    Context       : $(if ($IsCloudShell) {'Azure Cloud Shell'} elseif ($OnWindows) {'Windows'} else {'niet-ondersteund'})"
Write-Host "    EXO-module    : $(if ($exoVersion) {$exoVersion} else {'niet geïnstalleerd'})"

if (-not ($OnWindows -or $IsCloudShell)) {
    Write-Err "Niet-ondersteunde omgeving."
    Write-Host "    Dit script werkt alleen in Windows PowerShell of in Azure Cloud Shell." -ForegroundColor Yellow
    Write-Host "    Voer het opnieuw uit vanaf een Windows-machine, of vanaf https://shell.azure.com (PowerShell)." -ForegroundColor Yellow
    exit 2
}

# --- Logging ----------------------------------------------------------------
$timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$LogFile   = Join-Path (Get-Location) "purview-repair-$timestamp.log"
Start-Transcript -Path $LogFile -Append | Out-Null

# Schrijf tekst naar een apart bestand zonder ooit een fout te genereren (de .log wordt door de transcriptie vastgehouden).
function Save-ErrorDetail {
    param($Text)
    if ([string]::IsNullOrWhiteSpace($Text)) { return $null }
    try {
        $f = [System.IO.Path]::ChangeExtension($LogFile, 'error.txt')
        "VOLLEDIGE FOUTDETAILS (voor ondersteuning)`n$Text" | Out-File -FilePath $f -ErrorAction Stop
        return $f
    } catch { return $null }
}

$connected = $false

# Voert één keer Set-AdminAuditLogConfig uit en classificeert het resultaat. EXO REST-cmdlets houden
# try/catch niet altijd aan, dus we inspecteren -ErrorVariable.
function Set-AuditIngestionOnce {
    param([bool]$Value)
    $ErrorActionPreference = 'Continue'
    # Verberg de ruwe waarschuwing "... kan tot 60 minuten duren om toe te passen" van de console (we vangen
    # die nog steeds op via -WarningVariable en printen onze eigen [OK]). Dit is een voorkeur, geen opdracht
    # decoratie of een stream-omleiding.
    $WarningPreference = 'SilentlyContinue'
    $err = $null; $warn = $null
    # BELANGRIJK: roep dit EXACT aan alsof het een handmatig getypt commando is. Het decoreren van de aanroep met
    # -WarningAction SilentlyContinue / 2>$null / | Out-Null zorgt ervoor dat EXO binnen een script een onterechte (403)
    # teruggeeft, terwijl het kale commando wel slaagt. -ErrorVariable / -WarningVariable
    # vangen alleen de streams op; ze veranderen niet hoe de cmdlet met de server praat.
    $null = Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $Value `
        -ErrorVariable err -WarningVariable warn
    $errText  = ($err  | ForEach-Object { $_.ToString() }) -join ' '
    $warnText = ($warn | ForEach-Object { $_.ToString() }) -join ' '
    if ($errText) { $script:LastFullError = $errText }

    $dehydrated = ($errText -match 'Enable-OrganizationCustomization')
    $serverSide = ($errText -match 'server side error|try again after some time')
    $forbidden  = (-not $serverSide) -and ($errText -match '403|Forbidden|Interdit|Access is denied|not authorized|insufficient')

    if     ($dehydrated) { $short = 'Tenant is niet-gehydrateerd (heeft Enable-OrganizationCustomization nodig).' }
    elseif ($forbidden)  { $short = '403 Verboden - probleem met machtigingen / rol / licenties.' }
    elseif ($serverSide) { $short = "Microsoft is bezig (serverfout, 'probeer het later opnieuw')." }
    elseif ($errText)    { $short = ($errText -split "`n")[0] }
    else                 { $short = '' }

    [pscustomobject]@{
        Ok = [string]::IsNullOrWhiteSpace($errText); Warning = $warnText; Error = $errText
        Short = $short; Dehydrated = $dehydrated; Forbidden = $forbidden; ServerSide = $serverSide
    }
}

# Schakel de instelling om en probeer alleen bij tijdelijke fouten opnieuw.
function Invoke-AuditToggle {
    param([bool]$Value, [string]$Label)
    $r = $null
    for ($i = 1; $i -le $RetryCount; $i++) {
        $r = Set-AuditIngestionOnce $Value
        if ($r.Ok)         { Write-Ok "$Label geaccepteerd door Microsoft."; return $r }
        if ($r.Dehydrated) { return $r }                       # aanroeper hydrateert en probeert opnieuw
        if ($r.Forbidden)  { Write-Warn2 "$Label geweigerd (machtigingen / rol / licenties) - wordt niet opnieuw geprobeerd."; return $r }

        Write-Warn2 "$Label mislukt (poging $i/$RetryCount): $($r.Short)"
        if ($i -lt $RetryCount) {
            Write-Info "$Label wordt over ${RetryWaitSec}s opnieuw geprobeerd."
            Start-Sleep -Seconds $RetryWaitSec
        }
    }
    return $r
}

# Handmatige checklist die wordt getoond wanneer Graph abonnement / licenties / tenantleeftijd niet automatisch kan controleren.
function Write-ManualChecklist {
    Write-Info "Controleer handmatig in het Microsoft 365-beheercentrum (admin.microsoft.com):"
    Write-Info "  - Facturering > Uw producten : het abonnement is Actief (niet geschorst/verlopen)."
    Write-Info "  - Er is een licentie toegewezen die Exchange Online bevat (de meeste M365/O365-abonnementen)."
    Write-Info "  - Installatiedatum : tenants die in de afgelopen ~72 uur zijn gemaakt, hebben audit mogelijk nog niet geprovisioneerd."
}

# Volledige diagnose wanneer inschakelen echt wordt geweigerd.
function Invoke-FailureDiagnostic {
    param($ErrInfo)
    Write-Step "Diagnose - waarom werd inschakelen geweigerd?"
    if ($ErrInfo -and $ErrInfo.Short) { Write-Info "Gerapporteerde fout : $($ErrInfo.Short)" }

    # Verbonden identiteit
    $upn = $null
    try {
        $ci  = Get-ConnectionInformation -ErrorAction Stop | Select-Object -First 1
        $upn = $ci.UserPrincipalName
        Write-Info "Verbonden als   : $upn"
        Write-Info "Tenant-id      : $($ci.TenantId)"
    } catch { Write-Info "Verbindingsinformatie kon niet worden gelezen." }

    # Tenant-/organisatiesconfiguratie
    try {
        $org = Get-OrganizationConfig -ErrorAction Stop
        Write-Info "Organisatie   : $($org.DisplayName) [$($org.Name)]"
        Write-Info "IsDehydrated   : $($org.IsDehydrated)$(if ($org.IsDehydrated) {'  <- voer Enable-OrganizationCustomization uit'})"
    } catch { Write-Info "Get-OrganizationConfig kon niet worden gelezen." }

    # Machtigingen: heeft het account een rol waarmee de auditconfiguratie kan worden ingesteld?
    if ($upn) {
        try {
            $roles = Get-ManagementRoleAssignment -RoleAssignee $upn -Delegating:$false -ErrorAction Stop |
                     Select-Object -ExpandProperty Role -Unique
            Write-Info "Rollen          : $([string]::Join(', ', $roles))"
            if ($roles -contains 'Audit Logs' -or $roles -contains 'Organization Configuration') {
                Write-Ok "Account heeft een rol die auditconfiguratie toestaat."
            } else {
                Write-Warn2 "Account mist 'Organization Configuration' / 'Audit Logs'."
                Write-Warn2 "=> Voeg het toe aan de rollengroep 'Organization Management' (Exchange-beheercentrum)."
            }
        } catch { Write-Info "Rollen konden niet worden geïnventariseerd ($($_.Exception.Message))." }
    }

    # Abonnement / licenties / tenantleeftijd via Microsoft Graph (best effort, niet-fataal).
    if (Get-Command Get-MgOrganization -ErrorAction SilentlyContinue) {
        try {
            if (-not (Get-MgContext -ErrorAction SilentlyContinue)) {
                Write-Info "Verbinding maken met Microsoft Graph (alleen lezen) voor controles op abonnement/licentie/leeftijd..."
                Connect-MgGraph -Scopes 'Organization.Read.All' -NoWelcome -ErrorAction Stop
            }
            $o = Get-MgOrganization -ErrorAction Stop | Select-Object -First 1
            if ($o.CreatedDateTime) {
                $age = (New-TimeSpan -Start $o.CreatedDateTime -End (Get-Date)).Days
                Write-Info "Tenant aangemaakt : $($o.CreatedDateTime) (~$age dagen geleden)"
                if ($age -lt 3) { Write-Warn2 "Tenant is erg nieuw (<3 dagen) - auditprovisionering is mogelijk nog niet klaar. Probeer het later opnieuw." }
            }
            foreach ($s in (Get-MgSubscribedSku -ErrorAction Stop)) {
                Write-Info "Licentie        : $($s.SkuPartNumber)  status=$($s.CapabilityStatus)  seats=$($s.PrepaidUnits.Enabled)"
                if ($s.CapabilityStatus -in 'Suspended','Deleted') {
                    Write-Warn2 "Abonnement '$($s.SkuPartNumber)' heeft status $($s.CapabilityStatus) - niet actief."
                }
            }
        } catch {
            Write-Info "Graph-controle niet beschikbaar: $($_.Exception.Message)"
            Write-ManualChecklist
        }
    } else {
        Write-Info "Microsoft Graph-module niet geïnstalleerd - kan abonnement/licentie/tenantleeftijd niet automatisch controleren."
        Write-ManualChecklist
    }
}

try {
    # --- Voorwaarden -----------------------------------------------------
    Write-Step "Voorwaarden controleren"
    if (-not $exoVersion) {
        Write-Warn2 "ExchangeOnlineManagement niet gevonden. Wordt geïnstalleerd voor de huidige gebruiker."
        if ($OnWindows -and $PSVersionTable.PSEdition -eq 'Desktop') {
            # Windows PowerShell 5.1 heeft vaak expliciet TLS 1.2 nodig om de PSGallery te bereiken.
            [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        }
        Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser -Force -AllowClobber
        Write-Ok "Module geïnstalleerd."
    } else {
        Write-Ok "ExchangeOnlineManagement aanwezig ($exoVersion)."
    }
    Import-Module ExchangeOnlineManagement

    # --- Verbinden -----------------------------------------------------------
    Write-Step "Verbinden met Exchange Online"
    # In Azure Cloud Shell hergebruikt de EXO-module automatisch de sessie-identiteit - GEEN apparaat
    # code. Forceer de apparaatcode alleen wanneer de gebruiker dit expliciet vraagt met -Device.
    $useDevice = [bool]$Device
    if ($useDevice -and $AdminUpn) {
        Write-Warn2 "Aanmelding met apparaatcode: open de hieronder getoonde URL en voer de code in."
        Connect-ExchangeOnline -UserPrincipalName $AdminUpn -Device -ShowBanner:$false
    } elseif ($useDevice) {
        Write-Warn2 "Aanmelding met apparaatcode: open de hieronder getoonde URL en voer de code in."
        Connect-ExchangeOnline -Device -ShowBanner:$false
    } elseif ($AdminUpn) {
        Connect-ExchangeOnline -UserPrincipalName $AdminUpn -ShowBanner:$false
    } else {
        Connect-ExchangeOnline -ShowBanner:$false
    }
    $connected = $true
    Write-Ok "Verbonden."

    # --- Diagnose --------------------------------------------------------
    Write-Step "Huidige status"
    $enabled      = (Get-AdminAuditLogConfig).UnifiedAuditLogIngestionEnabled
    $isDehydrated = (Get-OrganizationConfig).IsDehydrated
    Write-Host "    UnifiedAuditLogIngestionEnabled : $enabled"
    Write-Host "    IsDehydrated                    : $isDehydrated"

    # --- Eerst alleen uitschakelen als het momenteel AAN staat (de uit->aan-cyclus ontgrendelt het portaal) -------
    if ($enabled) {
        Write-Step "Stap 1 - Inname uitschakelen (om een schone uit->aan-cyclus af te dwingen)"
        $d = Invoke-AuditToggle -Value $false -Label "Uitschakelen"
        if (-not $d.Ok) { Write-Warn2 "Uitschakelen werd niet bevestigd ($($d.Short)) - doorgaan met herinschakelen." }
    } else {
        Write-Info "Auditlogging staat momenteel UIT - wordt direct ingeschakeld (uitschakelen niet nodig)."
    }

    # --- Inschakelen ------------------------------------------------------------
    Write-Step "Stap 2 - Inname inschakelen"
    $e = Invoke-AuditToggle -Value $true -Label "Inschakelen"
    if ($e.Dehydrated) {
        Write-Warn2 "Niet-gehydrateerde tenant. Enable-OrganizationCustomization wordt uitgevoerd."
        try { Enable-OrganizationCustomization } catch { Write-Warn2 $_.Exception.Message }
        $h = 0
        do {
            Start-Sleep -Seconds 15
            $isDehydrated = (Get-OrganizationConfig).IsDehydrated
            $h++
            Write-Info "Wachten op hydratatie... poging $h (IsDehydrated = $isDehydrated)"
        } while ($isDehydrated -and $h -lt 8)
        if ($isDehydrated) { throw "Tenant is na het wachten nog steeds niet-gehydrateerd. Voer het over een paar minuten opnieuw uit." }
        Write-Ok "Tenant gehydrateerd."
        $e = Invoke-AuditToggle -Value $true -Label "Opnieuw inschakelen"
    }

    # --- Resultaat ------------------------------------------------------------
    Write-Step "Resultaat"
    Start-Sleep -Seconds 5
    $final = (Get-AdminAuditLogConfig).UnifiedAuditLogIngestionEnabled
    Write-Host "    UnifiedAuditLogIngestionEnabled (verse uitlezing) : $final"

    # De verse uitlezing is de bron van waarheid: als die True is, DAN is auditlogging ingeschakeld, zelfs wanneer de
    # laatste inschakeloproep $e.Ok false heeft gelaten (tijdelijke 403, "probeer het opnieuw"-ruis op de foutstroom).
    if ($final) {
        Write-Ok "Auditlogging is INGESCHAKELD. Klaar."
    }
    elseif ($e.Ok) {
        Write-Warn2 "Herinschakelen is geaccepteerd maar leest nog steeds False - normaal: Microsoft kan tot"
        Write-Host  "    ~60 minuten nodig hebben om het door te voeren. Controleer het zo meteen opnieuw met het onderstaande commando." -ForegroundColor Yellow
    }
    else {
        Write-Err "Inschakelen is NIET geaccepteerd door Microsoft. Diagnose wordt uitgevoerd."
        Save-ErrorDetail $script:LastFullError | Out-Null
        Invoke-FailureDiagnostic $e
    }

    # --- Later verifiëren ------------------------------------------------------
    Write-Step "Later verifiëren (maak eerst opnieuw verbinding, het script verbreekt de verbinding aan het einde)"
    if ($AdminUpn) {
        Write-Host  "        Connect-ExchangeOnline -UserPrincipalName $AdminUpn -ShowBanner:`$false" -ForegroundColor Gray
    } else {
        Write-Host  "        Connect-ExchangeOnline -ShowBanner:`$false" -ForegroundColor Gray
    }
    Write-Host  "        Get-AdminAuditLogConfig | Format-List UnifiedAuditLogIngestionEnabled" -ForegroundColor Gray
    Write-Host  "    'True' betekent dat het is ingeschakeld."
}
catch {
    Write-Err "Er is iets misgegaan: $($_.Exception.Message)"
    if ($connected) { try { Invoke-FailureDiagnostic $null } catch { } }
    $errorFile = Save-ErrorDetail $script:LastFullError
    Write-Host ""
    Write-Host "    >> Als je hulp nodig hebt, stuur het logbestand naar $SupportEmail :" -ForegroundColor Yellow
    Write-Host "       $LogFile" -ForegroundColor Yellow
    if ($errorFile) { Write-Host "       $errorFile" -ForegroundColor Yellow }
}
finally {
    if ($connected) {
        Write-Step "Verbinding verbreken"
        try { Disconnect-ExchangeOnline -Confirm:$false } catch { }
        Write-Ok "Verbroken."
    }
    Write-Host "`nLog opgeslagen naar: $LogFile" -ForegroundColor Gray
    Stop-Transcript | Out-Null
}
```

{% endcode %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.stoik.io/help/help-nl/email-security/microsoft-365/purview-script.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
