Using your API
If you haven't already configured your app, see Configuring my APP
Authentication
The API uses JWT (JSON Web Token) authentication. First, sign in to get an access token:
POST /api/auth/signin
Content-Type: application/json
{
"TenantId": "Demo",
"Username": "admin",
"Password": "Admin@123"
}
This returns an access token and refresh token. Include the access token in the Authorization header for all API requests:
GET /api/Customer
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Third-party clients such as MCP connectors don't sign in with a password — they get delegated, scoped access through OAuth 2.1. The API accepts both token formats on the same endpoints.
API Examples
All content sent via the body should be in json format
# Get all product
GET /api/Product
# Get a specific product
GET /api/Product(123e4567-e89b-12d3-a456-426614174000)
# Create a new product
POST /api/Product
Content-Type: application/json
{
"SKU": "ADJWRE",
"SalesDescription": "Adjustable Wrench",
"Cost": 24.65
}
# Update a product
PATCH /api/Product(123e4567-e89b-12d3-a456-426614174000)
Content-Type: application/json
{
"Cost": 22.85
}
# Delete a product
DELETE /api/Product(123e4567-e89b-12d3-a456-426614174000)
Natural-key references
JSON writes can identify a reference by the target entity's [NaturalKey] value:
{
"FirstName": "Alex",
"LastName": "Smith",
"MailingAddress": {
"Street": "10 Main Street",
"Country": "US"
}
}
Here, Country resolves to the existing country's GUID before the contact is saved.
This also works for references inside collection objects and for writes sent through $batch.
Missing keys and keys matching multiple records return validation errors.
GUID references continue to work. A GUID-shaped string is treated as a GUID first.
OData Query Examples
# Filter product
GET /api/Product?$filter=Cost lt 25.00
# Sort product
GET /api/Product?$orderby=Sku
# Select specific fields
GET /api/Product?$select=Sku,SalesDescription,Cost
# Expand related entities (References or collections)
GET /api/Product?$expand=Uoms($select=Name)
# Combine queries
GET /api/Product?$filter=Cost lt 25.00&$orderby=Sku&$top=10&$select=Sku,SalesDescription
# Count
GET /api/Products?$count=true
# Aggregates
GET api/SalesOrderDetail?$apply=filter(Product/Sku eq 'EAST')/aggregate(Quantity with sum as QuantityOfEasterEggers)
A few tips:
$select
- Entities and properties are case sensitive.
- If select is not specified, then only the
Guidproperty is returned. - All entities have a
Guidand aTitleproperty.$select=Guid,Title - Both virtual and persisted properties can be selected. However, returning only persisted properties is more performant.
$filter, $orderby, $apply, and $compute
- For database-backed entities, filtering, ordering, grouping, aggregation, and computation must use persisted properties as inputs.
- This includes property paths through related entities and expressions inside
$apply. - Virtual (computed) properties remain readable through
$selectand$expand. Using them in these server operations returns HTTP400 Bad Requestwith a validation error. - Aliases computed from supported persisted-property expressions remain usable; they are not virtual entity properties.
- You can do a joined filter like this:
Customer?$filter=PrimaryContact/MailingAddress/State eq 'PA'
$expand
- Expand is used for both collections and references:
SalesOrder?$expand=Details($select=Description)SalesOrder?$expand=PriceLevel($select=Name) - Expand and select multiple levels:
Customer?$select=Id&$expand=PrimaryContact($select=FullName;$expand=MailingAddress($select=City,State))
$top and skip
- Used for paged queries
OData Batch
Use POST /api/$batch to send several OData requests in one HTTP request.
Dashboards use $batch for read-only GET requests. Import tools can also use it for normal OData writes, such as POST, PATCH, and DELETE. Batched writes go through the same generated controllers as normal writes, so permissions, feature checks, deserialization hooks, business logic, and save logic still run.
The default batch limit is 64 parts. If an import has more work than that, split it into smaller batches. The same limit also applies to the number of write operations inside one changeset.
Workflows
Have the client interact with entities on the server by sending each change to the API and receiving updated entities. The server stages each change in durable workflow state until the workflow is committed, so edits survive a restart and any server can continue the workflow. See Workflows
See also
OData documentation: Getting Started · OData - the Best Way to REST
OData Aggregates: Grouping and Aggregation in OData Client - OData | Microsoft Learn