Skip to content

Configuration

Primary defaults live in appsettings.json. For local development, override with appsettings.Development.json and user secrets or environment variables. In the deployed container, prefer environment variables and keep secrets out of the repo.


Connection strings

"ConnectionStrings": {
  "DefaultConnection": "Server=sql-host,1433;Database=customstuf;User Id=app;Password=***;TrustServerCertificate=true",
  "TarbelConnection":  "Server=sql-host,1433;Database=tarbel;User Id=app;Password=***;TrustServerCertificate=true"
}

appsettings.Development.json is committed and must only ever contain the REPLACE_WITH_MSSQL_SA_PASSWORD placeholders — never a real password. For local development put the real connection strings in user secrets (loaded automatically for the Development and dv environments):

cd src/CustomsHive.Web
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=...;Database=customstuf_dv;User Id=...;Password=...;TrustServerCertificate=true"
dotnet user-secrets set "ConnectionStrings:TarbelConnection"  "Server=...;Database=tarbel;User Id=...;Password=...;TrustServerCertificate=true"

The Tarbel database is managed by the application. Admins upload the ZIP from the minfin portal via /Admin/Tarbel; the TarbelImportWorker imports GoodsNomenclature, GeographicalArea, MeasureType, AdditionalCode, Certificate, Measure, and related tables into the Tarbel database configured by TarbelConnection.


Auth

Set Auth:Provider to select the authentication backend:

Value Backend
EntraId Microsoft Entra ID (Azure AD)
(empty) Treated as Entra ID

Who can reach which admin page

Two policies gate /Admin/*. RequiresAdmin is the narrower one; RequiresSuperUserOrAdmin admits both roles.

page policy why
ApiKeys, DescartesSettings, IngestionSettings, Prompts RequiresAdmin Credentials, the broker connection and the extraction prompts. All four already restricted editing to Admins — the page itself now matches, because reading an API key list or a broker connection string is not a lesser act than editing it.
CustomsLocations, CustomsTeams RequiresSuperUserOrAdmin Declarant-operated reference data: sites, teams and their Descartes folders. Deliberately left with SuperUsers — the people who run declarations maintain these.
Appearance, AuditLog, Claims, Costs, DataSync, DeclarationFields, ProvenanceTypes RequiresSuperUserOrAdmin Operational settings and read-only views.

Entra ID

"Auth": {
  "Provider": "EntraId",
  "EntraId": {
    "Instance":    "https://login.microsoftonline.com/",
    "TenantId":    "",
    "ClientId":    "",
    "ClientSecret": "",
    "CallbackPath": "/signin-oidc",
    "CertificateThumbprint": ""   // optional — use cert instead of secret
  }
}

Only Entra ID authentication is supported by the current code.


AI provider

Set AI:Provider to either AzureOpenAI or OpenAI.

  • AzureOpenAI: Azure endpoint + deployment
  • OpenAI: OpenAI API key and optional OpenAI-compatible BaseUrl
  • If AI:Provider is empty, the app auto-detects provider from configured keys
"AI": {
  "Provider": "AzureOpenAI",
  "AzureOpenAI": {
    "Endpoint":       "https://your-resource.openai.azure.com/",
    "DeploymentName": "gpt-4o",
    "UseKeylessAuth": true,   // true = app registration (ClientSecretCredential)
    "ApiKey":         ""      // set if UseKeylessAuth = false
  },
  "OpenAI": {
    "BaseUrl": "",
    "ApiKey": "",
    "Model": "gpt-4o-mini"
  }
}

When UseKeylessAuth is true, the client authenticates via ClientSecretCredential using the shared Azure:* credentials block (see below). When false, ApiKey is required.


Azure credentials (shared)

Both Azure OpenAI and Azure Document Intelligence use the same app registration when UseKeylessAuth is true:

"Azure": {
  "TenantId":     "",
  "ClientId":     "",
  "ClientSecret": ""
}

Azure Document Intelligence

Used for native PDF OCR/layout analysis before sending text to Azure OpenAI. Optional — if not configured, the app falls back to GPT vision (PDF rasterised to JPEG).

"DocumentIntelligence": {
  "Endpoint":       "https://your-resource.cognitiveservices.azure.com/",
  "UseKeylessAuth": true,         // uses Azure:* credentials block above
  "ApiKey":         "",           // set if UseKeylessAuth = false
  "ModelId":        "prebuilt-layout"
}
Key Default Description
Endpoint Required. Azure Document Intelligence resource endpoint URL
UseKeylessAuth true When true, uses shared Azure:* ClientSecretCredential
ApiKey Required when UseKeylessAuth is false
ModelId prebuilt-layout Document model to use for analysis

Storage

"Storage": {
  "UploadFolder":      "./uploads",
  "MaxFileSizeBytes":  52428800
}

UploadFolder is where uploaded PDFs/XLSX files and temp upload files are stored.


Reverse proxy trust

"Proxy": {
  "KnownNetworks": [ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "::1/128" ]
}

CIDR networks whose peers are trusted to set X-Forwarded-For / X-Forwarded-Proto (the reverse proxy). Requests from outside these networks have their forwarded headers ignored, so internet clients cannot spoof their IP. Docker env-var form: Proxy__KnownNetworks__0=10.0.0.0/8 etc. If your proxy runs on a public IP, add its address as a /32.


Branding

Branding is configured from appsettings.json (not from Admin settings):

"Branding": {
  "TitlePlain": "CustomsTUF",
  "TitleHtml": "<span class=\"brand-prefix\">Customs</span><span class=\"brand-suffix\">TUF</span>",
  "MainColor": "#119E50"
}
  • TitlePlain: plain-text title (e.g. login page and metadata)
  • TitleHtml: rendered in the main layout header
  • MainColor: global accent color

Descartes SMF XSD validation

SMF schema validation is optional and controlled by config:

"Descartes": {
  "Smf": {
    "ValidateXsd": false,
    "XsdFolder": "./schemas/smf"
  }
}

For Docker images built from this repo: - schemas/smf/*.xsd are bundled into publish output - runtime defaults are set via env vars in Dockerfile: - Descartes__Smf__ValidateXsd=true - Descartes__Smf__XsdFolder=/app/schemas/smf


Processing

"Processing": {
  "MaxConcurrentJobs": 4,
  "PdfDpi": 300
}

PdfDpi controls the resolution at which PDFs are rasterised before being sent to the AI vision model. 300 DPI is recommended — lower values cause digit misreads.


Country aliases

The AI sometimes returns country names that differ from the Tarbel database entries (e.g. TURKIYE instead of TURKEY, USA instead of UNITED STATES). Add aliases here without a rebuild:

"CountryAliases": {
  "TURKIYE": "TR",
  "USA":     "US",
  "UK":      "GB"
}

Keys are case-insensitive. Values must be valid ISO 3166-1 alpha-2 codes. When the AI returns a name not in the Tarbel GeographicalArea table, the resolution order is:

  1. Already a 2-letter ISO2 code → pass through
  2. CountryAliases lookup (this section)
  3. Exact match on DescriptionEN or DescriptionNL in Tarbel
  4. Partial / starts-with match in Tarbel
  5. Log warning, leave as-is

Service Bus — ingestion queue

Configures the inbound canonical queue consumed by CanonicalServiceBusReceiverService. Uses the shared Azure:* credentials block.

"ServiceBus": {
  "Ingestion": {
    "ActiveEnvironment": "prd",
    "prd": {
      "FullyQualifiedNamespace": "your-servicebus.servicebus.windows.net",
      "QueueName": "customshive-inbound"
    }
  }
}

Multiple named environments (dv, stg, prd) can be defined; only ActiveEnvironment is connected at runtime. Connectivity can be tested via /Admin/IngestionSettingsRun peek probe.

Key Description
ServiceBus:Ingestion:ActiveEnvironment Name of the active environment block
ServiceBus:Ingestion:{env}:FullyQualifiedNamespace Service Bus namespace FQDN
ServiceBus:Ingestion:{env}:QueueName Inbound queue name
ServiceBus:Ingestion:MaxConcurrentCalls Inbound messages handled at once (1–32, default 4)

Automatic dispatch target

An ingestion message can ask for a transit declaration to be filed without a declarant reviewing it (auto_dispatch — see servicebus-integration.md). Where that declaration is filed is named explicitly:

Key Default Description
ServiceBus:Descartes:AutoDispatchEnvironment Pr Which Descartes environment an auto-dispatched declaration goes to. Must name a configured ServiceBus:Descartes:{env} block; when it does not, the receiver logs a warning at startup and refuses every automatic dispatch rather than picking another environment.

An instance normally has both Dev and Pr configured, so this is not inferable — and a declaration filed without a declarant landing on the wrong environment is not visible as a failure anywhere. Refusing is the safe outcome: the dossier is still created, and a declarant can file it.

The former ingestion:auto_dispatch AppSetting was removed in WP62. It was seeded and read by nothing, so the Admin toggle never did anything; auto-dispatch is now a property of the message. The row is deleted on startup.


Ingestion API keys

API keys for POST /api/declarations are managed entirely via /Admin/ApiKeys — no appsettings entry is needed. Keys are stored hashed (SHA-256); the raw key is shown only once at creation.

See ingestion-api.md for usage details.


Declarations (company-wide defaults)

Stored in the AppSettings table (editable via /Admin/DeclarationFields), not in appsettings.json. Prefix: declarations:.

Key Description
declarations:sender_gln GLN of the sender (MessageHeader)
declarations:declarant_eori Declarant / representative EORI
declarations:declarant_name Declarant name as it appears in XML declarations
declarations:declarant_street Declarant street and house number
declarations:declarant_city Declarant city
declarations:declarant_postcode Declarant postcode (also issuingPlaceCode)
declarations:crin Declarant CRIN reference number (Descartes SMF)
declarations:auth_c501 Authorisation C501 reference (AEO-C)
declarations:auth_c505 Authorisation C505 reference (guarantee)
declarations:auth_c517 Authorisation C517 reference (AGP)
declarations:auth_c506 Authorisation C506 reference (REM)
declarations:supervising_customs_office Default supervising customs office ref
declarations:warehouse Default warehouse identifier (H2B)
declarations:location_of_goods Default UN/LOCODE for location of goods
declarations:type_part_three TypePartThree code in SMF declarations (e.g. T2LF)
declarations:contact_phone Contact phone number for declarations
declarations:contact_name Contact person name for declarations
declarations:contact_email Contact person email for declarations

Tarbel settings

Stored in the AppSettings table (editable via /Admin/Tarbel).

Key Default Description
tarbel:auto_download false When true, automatically downloads and imports any pending Tarbel extractions during the daily sync cycle (runs at 02:00 UTC). When false, the worker still refreshes the latest available extraction key each night (so the admin banner stays current) but does not download. The download can always be triggered manually via the Download & import button in /Admin/Tarbel.

UN/LOCODE settings

Stored in the AppSettings table (editable via /Admin/Locode).

Key Default Description
locode:auto_sync false When true, automatically checks and re-imports UN/LOCODE data when a new version is published.
locode:datapackage_url (datahub.io URL) URL of the datapackage.json used to check for a new version.
locode:csv_url (datahub.io URL) Direct URL of the code-list.csv file to download.