On this page:
6.1 The value of interactive exploration
6.2 Inspecting an entity
6.2.1 Understanding the output
6.2.2 OData types
6.2.3 Enum types
6.3 Previewing data
6.3.1 Visual preview with peek
6.3.2 Raw data with sample
6.4 Generating documentation
6.4.1 Racket comment format
6.4.2 Scribble format
6.5 Fetching raw metadata
6.5.1 All standard entities
6.5.2 Individual entity schema
6.5.3 Examining properties
6.5.4 Examining navigation properties
6.6 Metadata caching
6.7 Custom API metadata
6.8 Practical examples
6.8.1 Find entities with a specific field
6.8.2 Find expandable relationships
6.8.3 Generate a type summary
6.9 When metadata helps
6.10 Next steps
9.3

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—without leaving the REPL.

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

string?

text

Edm.Guid

string?

UUID format string

Edm.Int32

exact-integer?

32-bit integer

Edm.Int64

exact-integer?

64-bit integer

Edm.Decimal

real?

decimal number

Edm.Double

flonum?

floating-point number

Edm.Boolean

boolean?

#t or #f

Edm.DateTimeOffset

string?

ISO 8601 datetime string

Edm.Date

string?

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:

(displayln (inspect->comment customers))

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:

(displayln (inspect->scribble customers))

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.