6 Metadata Discovery
Business Central’s API exposes dozens of entities, each with many fields and relationships. When
you’re exploring an unfamiliar part of the API or trying to remember what fields are available,
bcnav’s metadata discovery features help you understand what you’re working with—
6.1 The value of interactive exploration
One of Racket’s strengths is its interactive REPL (Read-Eval-Print Loop). You can type an expression, see the result immediately, refine your approach, and build up to a working solution incrementally.
Metadata discovery makes this workflow shine for BC API exploration:
6.2 Inspecting an entity
The inspect function prints a formatted summary of an entity’s schema:
(require bcnav bcnav/api/master-data) ;; After authentication... (inspect customers)
This displays output like:
╭────────────────────────────────────────────╮ |
│ customers (standard v2.0 API) │ |
╰────────────────────────────────────────────╯ |
Key: id |
|
╭──────────────────┬───────────────────┬────────────────┬──────────╮ |
│ Field │ OData Type │ Racket Type │ Nullable │ |
├──────────────────┼───────────────────┼────────────────┼──────────┤ |
│ id │ Edm.Guid │ string? │ no │ |
│ number │ Edm.String │ string? │ yes │ |
│ displayName │ Edm.String │ string? │ yes │ |
│ type │ Edm.String │ string? │ yes │ |
│ addressLine1 │ Edm.String │ string? │ yes │ |
│ ... │ │ │ │ |
╰──────────────────┴───────────────────┴────────────────┴──────────╯ |
|
Navigation Properties (use with $expand): |
╭──────────────────┬───────────────┬──────────────╮ |
│ Name │ Target │ Multiplicity │ |
├──────────────────┼───────────────┼──────────────┤ |
│ currency │ currency │ one │ |
│ paymentTerm │ paymentTerm │ one │ |
│ picture │ picture │ many │ |
│ ... │ │ │ |
╰──────────────────┴───────────────┴──────────────╯ |
6.2.1 Understanding the output
The output shows:
Header: The entity name and whether it’s a standard or custom API
Key: The field(s) that uniquely identify a record (usually id)
Fields: All properties with their OData type, corresponding Racket type, and nullability
Navigation Properties: Related entities you can include with #:expand. one means a single related record; many means a collection.
6.2.2 OData types
BC uses standard OData/EDM (Entity Data Model) types. The inspect output shows both the OData type and the corresponding Racket type predicate:
OData Type | Racket Type | Description |
Edm.String | text | |
Edm.Guid | UUID format string | |
Edm.Int32 | 32-bit integer | |
Edm.Int64 | 64-bit integer | |
Edm.Decimal | decimal number | |
Edm.Double | floating-point number | |
Edm.Boolean | #t or #f | |
Edm.DateTimeOffset | ISO 8601 datetime string | |
Edm.Date | YYYY-MM-DD format string |
In practice, you’ll work with these as Racket strings, numbers, and booleans. JSON parsing handles the conversion automatically.
6.2.3 Enum types
Some fields use Business Central enum types (e.g., Microsoft.NAV.assemblyPolicy). These appear as vendor-specific types in the OData schema and are returned as strings in API responses.
When inspect encounters an enum field, it displays the allowed values with their numeric codes:
│ assemblyPolicy │ Microsoft.NAV.assemblyPolicy │ (or/c exact-integer? string?) │ yes │ |
│ │ 0: Assemble-to-Stock │ │ │ |
│ │ 1: Assemble-to-Order │ │ │ |
You can filter on enum fields using either the numeric value (recommended) or the encoded string value:
;; Using numeric value (preferred - clean and unambiguous) (items-list #:query (make-query #:filter (eq 'assemblyPolicy 0))) ;; Using string value (requires encoding special characters) (items-list #:query (make-query #:filter (eq 'assemblyPolicy (bc-encode-enum "Assemble-to-Stock"))))
BC encodes special characters in enum values (e.g., - becomes _x002D_). Use bc-encode-enum and bc-decode-enum to convert between human-readable and API-encoded forms:
;; Encode for API use (bc-encode-enum "Assemble-to-Stock") ;; => "Assemble_x002D_to_x002D_Stock" ;; Decode API responses for display (bc-decode-enum "Assemble_x002D_to_x002D_Stock") ;; => "Assemble-to-Stock"
To programmatically look up allowed values for an enum field:
(enum-values items 'assemblyPolicy) ;; => ’((0 . "Assemble-to-Stock") (1 . "Assemble-to-Order"))
6.3 Previewing data
bcnav provides two ways to preview actual data from an entity: peek for visual display and sample for programmatic access.
6.3.1 Visual preview with peek
The peek function fetches records and displays them as a formatted table:
;; Display 5 records (the default) (peek customers) ;; Display 10 records (peek customers 10)
This outputs a nicely formatted table:
╭────────────────────────────────────────────────────────────╮ |
│ customers (standard v2.0 API) - 5 records │ |
╰────────────────────────────────────────────────────────────╯ |
╭──────────────────┬──────────┬───────────────────┬──────────╮ |
│ id │ number │ displayName │ email │ |
├──────────────────┼──────────┼───────────────────┼──────────┤ |
│ a1b2c3d4-... │ C00010 │ Contoso Ltd │ info@... │ |
│ e5f6g7h8-... │ C00020 │ Fabrikam Inc │ sales@...│ |
│ ... │ │ │ │ |
╰──────────────────┴──────────┴───────────────────┴──────────╯ |
peek is ideal for interactive exploration in the REPL. It automatically aligns columns based on data types (right-aligning numbers, left-aligning text) and truncates long values to keep the table readable.
6.3.2 Raw data with sample
The sample function fetches and returns records as a list of hash tables:
;; Get 5 records (the default) (sample customers) ;; Get 10 records (sample customers 10)
This returns a list of hash tables, just like calling the entity’s list function. It’s useful when you need to process the data programmatically:
;; See the first customer’s fields (define examples (sample customers 1)) (hash-keys (first examples)) ;; Work with the data (for ([cust (sample customers 3)]) (printf "~a: ~a\n" (hash-ref cust 'number) (hash-ref cust 'displayName)))
6.4 Generating documentation
bcnav can generate schema documentation in formats suitable for copy/paste into your code.
6.4.1 Racket comment format
Use inspect->comment to generate a comment block you can paste into a module:
This outputs:
;; ============================================================ |
;; Entity: customers (standard v2.0 API) |
;; Key: id |
;; |
;; Fields: |
;; id : Edm.Guid string? |
;; number : Edm.String string? (nullable) |
;; displayName : Edm.String string? (nullable) |
;; type : Edm.String string? (nullable) |
;; ... |
;; |
;; Navigation Properties: |
;; currency -> currency (one) |
;; paymentTerm -> paymentTerm (one) |
;; ... |
;; ============================================================ |
Paste this at the top of a module that works with customers, so you have a quick reference without needing to run inspect again.
6.4.2 Scribble format
Use inspect->scribble to generate Scribble documentation code:
This outputs Scribble code with "@" subsection and "@" tabular forms that you can paste into a .scrbl file.
6.5 Fetching raw metadata
For programmatic access to schema information, bcnav provides lower-level functions.
6.5.1 All standard entities
Use fetch-metadata to get metadata for all standard BC API entities:
(define metadata (fetch-metadata)) (hash-keys metadata)
This returns a hash table mapping entity names (as symbols) to entity-info structs:
> (sort (map symbol->string (hash-keys metadata)) string<?) |
'("account" |
"agedAccountsPayable" |
"agedAccountsReceivable" |
"attachments" |
"bankAccount" |
"company" |
"companyInformation" |
"contact" |
"currency" |
"customer" |
...) |
6.5.2 Individual entity schema
Use entity-schema to get schema for a specific entity:
(define cust-info (entity-schema 'customer))
This returns an entity-info struct (or #f if the entity doesn’t exist):
> (entity-info-name cust-info) |
'customer |
|
> (entity-info-key-properties cust-info) |
'(id) |
|
> (length (entity-info-properties cust-info)) |
42 |
|
> (length (entity-info-navigation-properties cust-info)) |
7 |
6.5.3 Examining properties
Each property is a property-info struct:
(for ([prop (entity-info-properties cust-info)] #:when (string-prefix? (symbol->string (property-info-name prop)) "email")) (printf "~a : ~a~a\n" (property-info-name prop) (property-info-type prop) (if (property-info-nullable? prop) " (nullable)" "")))
6.5.4 Examining navigation properties
Navigation properties tell you what can be expanded:
(for ([nav (entity-info-navigation-properties cust-info)]) (printf "~a -> ~a (~a)\n" (nav-property-info-name nav) (nav-property-info-target-type nav) (nav-property-info-multiplicity nav)))
6.6 Metadata caching
Fetching metadata requires an API call that returns a large XML document. bcnav caches the parsed metadata to avoid repeated fetches:
;; First call fetches from the API (fetch-metadata) ;; Subsequent calls use the cache (fetch-metadata) ;; instant ;; Clear the cache if needed (e.g., after BC updates) (clear-metadata-cache!) ;; Next call fetches fresh data (fetch-metadata)
The cache is stored in the current-bc-metadata-cache parameter, so it’s cleared when your program ends.
6.7 Custom API metadata
Custom and publisher APIs have their own metadata endpoints. bcnav handles this automatically when you use inspect, peek, or sample with a custom entity:
(require bcnav/api/custom) (define-custom-api my-api "contoso" "inventory" "v1.0") (define-entity widget my-api "widgets") ;; Works the same as standard entities (inspect widget) (peek widget) (sample widget)
For programmatic access to custom API metadata:
;; Fetch metadata for a custom API (fetch-custom-metadata "contoso" "inventory" "v1.0") ;; Get schema for a specific custom entity (custom-entity-schema "contoso" "inventory" "v1.0" 'widgets)
Custom API metadata is cached separately from the standard API metadata.
6.8 Practical examples
6.8.1 Find entities with a specific field
Which entities have an email field?
(define metadata (fetch-metadata)) (for ([(name info) (in-hash metadata)]) (for ([prop (entity-info-properties info)] #:when (eq? (property-info-name prop) 'email)) (printf "~a has email field\n" name)))
6.8.2 Find expandable relationships
What can I expand from sales orders?
(require bcnav/api/sales) ;; Visual output (inspect sales-order) ;; Or programmatically (define so-info (entity-schema 'salesOrder)) (printf "Sales Order can expand:\n") (for ([nav (entity-info-navigation-properties so-info)]) (printf " ~a (~a ~a)\n" (nav-property-info-name nav) (nav-property-info-multiplicity nav) (nav-property-info-target-type nav)))
6.8.3 Generate a type summary
Count fields by type across all entities:
(define metadata (fetch-metadata)) (define type-counts (make-hash)) (for* ([(name info) (in-hash metadata)] [prop (entity-info-properties info)]) (define type (property-info-type prop)) (hash-update! type-counts type add1 0)) (for ([(type count) (in-hash type-counts)]) (printf "~a: ~a fields\n" type count))
6.9 When metadata helps
Use metadata discovery when you:
Start working with a new entity and need to know its fields
Want to quickly see what real data looks like (peek)
Want to build a query but aren’t sure what to filter on
Need to know what relationships exist for #:expand
Are debugging why a field name isn’t working (typo? wrong entity?)
Want to generate documentation for your custom BC integrations
6.10 Next steps
Continue to Custom APIs to learn how to access publisher and custom APIs beyond the standard BC v2.0 API.