A .NET 9 Web API for managing QuickBooks Custom Field Definitions and Invoices using the App Foundations GraphQL API and QuickBooks REST API v3.
- Complete CRUD Operations: Create, Read, Update, Delete (soft delete) for QuickBooks custom field definitions
- App Foundations API Integration: Full implementation using QuickBooks App Foundations GraphQL API
- Advanced Association Management: Complex entity relationships (Transactions, Contacts, Vendors, Customers)
- Production-Ready Soft Delete: Safe deactivation-based deletion preserving data integrity
- Schema Compliance: Complete
AppFoundations_CustomFieldDefinitionInputschema support - Automatic Field Management: Auto-retrieval of required fields (
legacyIDV2, field preservation) - Business Rule Enforcement: Association validation and mutual exclusivity checking
- Invoice CRUD Operations: Create, read, update invoices with QuickBooks REST API v3
- Custom Fields Integration: Attach custom field values to invoices with
DefinitionIdandName - Multi-Line Item Support: Full support for multiple line items with proper
Line.Idhandling - Items/Services Management: Dynamic item/service selection from QuickBooks
- Real-time Calculations: Automatic line item and invoice total calculations
- Interactive UI: Modern web interface for invoice and custom field management
- Dual API Support: App Foundations GraphQL + REST API v3
- OAuth 2.0 with App Foundations Scopes: Secure authentication with granular permissions
- RESTful API: Clean REST endpoints for seamless integration
- Comprehensive Error Handling: GraphQL error parsing and detailed validation messages
- Interactive Documentation: Swagger UI with complete CRUD testing examples
- Modern Web UI: Bootstrap 5 interface with real-time updates
Custom field capabilities vary by QuickBooks subscription tier:
| SKU | Transactional Custom Fields | Customer/Entity Custom Fields |
|---|---|---|
| Simple Start | Text, Dropdown; limit 1 per transaction | Text, Number, Date, Dropdown; up to 30 customer fields (from July 1, 2025) |
| Essentials | Text, Dropdown; limit 4 per transaction (historically 3 total) | Same as Simple Start |
| Plus | Text, Dropdown; limit 4 per transaction (historically 3 total) | Same as Simple Start |
| Advanced | Text, Number, Date, Dropdown; up to 12 per transaction | Text, Number, Date, Dropdown; up to 12 per entity (customer/vendor/project) |
| Intuit Enterprise Suite (IES) | Text, Number, Date, Dropdown; up to 12 per transaction | Text, Number, Date, Dropdown; up to 12 per entity |
Before running this application, ensure you have the following installed:
- .NET 9 SDK or later - Download
- QuickBooks Developer Account - Sign up
- QuickBooks App - Created in your developer account with:
- OAuth 2.0 configured
- App Foundations scopes enabled
- Redirect URI configured
- ngrok - For production redirect URL
-
Create a QuickBooks App:
- Go to Intuit Developer Portal
- Create a new app or use an existing one
- Note your
Client IDandClient Secret
-
Configure OAuth Settings:
- Set Redirect URI:
http://localhost:5039/api/oauth/callback - Enable required scopes:
app-foundations.custom-field-definitions.readapp-foundations.custom-field-definitionscom.intuit.quickbooks.accounting
- Set Redirect URI:
-
Environment Note:
- App Foundations Custom Field API requires Production environment
- Invoice operations work in both Sandbox and Production
- Configure environment in
appsettings.json
- GraphQL.Client - GraphQL client for .NET
- IppDotNetSdkForQuickBooksApiV3 - Official Intuit .NET SDK for OAuth
- Newtonsoft.Json - JSON serialization
- Microsoft.AspNetCore - Web API framework
GET /api/customfields/definitions- Get all custom field definitions with associationsGET /api/customfields/definitions/{id}- Get specific custom field definition by IDGET /api/customfields/summaries- Get simplified summaries (optimized for UI)POST /api/customfields- Create a new custom field definitionPUT /api/customfields/{id}- Update an existing custom field definitionDELETE /api/customfields/{id}- Delete (deactivate) a custom field definition
GET /api/invoice/list?page={page}&pageSize={pageSize}- Get paginated invoice listPOST /api/invoice/create- Create a new invoice with line items and custom fieldsPUT /api/invoice/update- Update an existing invoiceGET /api/customer/list- Get all active customersGET /api/item/list- Get all active items/services
GET /api/oauth/authorize- Initiate OAuth flowGET /api/oauth/callback- OAuth callback handlerGET /api/oauth/status- Check authentication statusPOST /api/oauth/refresh- Refresh access tokenPOST /api/oauth/disconnect- Disconnect from QuickBooks
# Clone the repository
git clone <repository-url>
# Navigate to the project directory
cd SampleApp-CustomFields-DotNet# Restore NuGet packages
dotnet restoreUpdate appsettings.json with your QuickBooks app credentials:
Important: The App Foundations Custom Field Definitions API requires the production environment as it's not available in sandbox mode.
{
"QuickBooks": {
"ClientId": "YOUR_CLIENT_ID",
"ClientSecret": "YOUR_CLIENT_SECRET",
"RedirectUri": "http://localhost:5039/api/oauth/callback",
"DiscoveryDocument": "https://appcenter.intuit.com/api/v1/connection/oauth2",
"BaseUrl": "https://sandbox-quickbooks.api.intuit.com",
"GraphQLEndpoint": "https://public.api.intuit.com/2020-04/graphql",
"ProductionGraphQLEndpoint": "https://qb.api.intuit.com/graphql",
"Environment": "production",
"CustomFieldScopes": [
"app-foundations.custom-field-definitions.read",
"app-foundations.custom-field-definitions",
"com.intuit.quickbooks.accounting"
]
}
}Configuration Steps:
- Replace
YOUR_CLIENT_IDwith your QuickBooks app's Client ID - Replace
YOUR_CLIENT_SECRETwith your QuickBooks app's Client Secret - Verify the
RedirectUrimatches your QuickBooks app configuration - Set
Environmentto"production"for custom fields or"sandbox"for testing invoices only
# Run on specific port
dotnet run --urls "http://localhost:5039"
# Or use default settings
dotnet runThe application will start and listen on http://localhost:5039
Web UI:
- Open your browser to
http://localhost:5039 - You'll see the interactive web interface
Swagger Documentation:
- Navigate to
http://localhost:5039/swagger - Interactive API documentation and testing
- Click "Connect to QuickBooks" in the web UI or navigate to
/api/oauth/authorize - Sign in with your QuickBooks account
- Authorize the application
- You'll be redirected back to the application
- Check authentication status at
/api/oauth/status
Via Web UI:
- Manage custom fields in the Custom Fields tab
- Create and manage invoices in the Invoices tab
Via API:
- Use Swagger UI to test API endpoints
- Use curl commands from the Testing section below
The App Foundations API provides advanced custom field capabilities:
- STRING: Text values
- NUMBER: Numeric values (stored as string, displayed as number)
- DATE: Date values (API format: yyyy-mm-dd, UI display: mm/dd/yyyy)
Custom fields can be associated with different QuickBooks entities and their sub-types. The API supports flexible association configurations:
- Transactions:
/transactions/TransactionSALE_INVOICE- Sales invoicesSALE_ESTIMATE- Sales estimates/quotesPURCHASE_ORDER- Purchase orders- And more transaction types...
- Contacts:
/network/ContactCUSTOMER- Customer contactsVENDOR- Vendor contactsEMPLOYEE- Employee contacts
{
"name": "Custom Field Name",
"type": "STRING",
"associations": [
{
"associatedEntity": "/transactions/Transaction",
"active": true,
"required": false,
"associationCondition": "INCLUDED",
"subAssociations": ["SALE_INVOICE", "SALE_ESTIMATE"]
}
]
}- ✅ Multiple Sub-Entities: Associate with multiple sub-types within the same entity
- ✅ Required/Optional Fields: Control field validation per association
- ✅ Active State Management: Enable/disable associations dynamically
- ✅ Conditional Logic:
INCLUDEDorEXCLUDEDassociation conditions - ❌ Mixed Entity Types: Cannot combine Contact and Transaction associations in one field
- Single Entity Type: Each custom field can only associate with one main entity type
- Multiple Sub-Associations: Within an entity type, you can associate with multiple sub-types
- Mutual Exclusivity: Some sub-associations are mutually exclusive (API will return specific errors)
The service uses the App Foundations API with complete CRUD support:
appFoundationsCustomFieldDefinitions: Primary query for retrieving all custom field definitions- Association Data: Complex entity relationships with sub-associations
- Legacy ID Support: Handles both
legacyIDandlegacyIDV2formats - Edge-based Results: GraphQL edges/nodes pattern for pagination
- Input:
AppFoundations_CustomFieldDefinitionCreateInput! - Required:
label,dataType,active,associations - Returns: Complete field definition with generated ID
- Features: Default associations, custom entity relationships
- Input:
AppFoundations_CustomFieldDefinitionUpdateInput! - Required:
id,legacyIDV2,label,active,dataType - Returns: Updated field definition
- Features: Partial updates, field preservation, association modification
- Implementation: Uses update mutation with
active: false - Reason: App Foundations API doesn't provide direct delete mutation
- Benefits: Data preservation, audit trails, reversibility
- Schema: Same as update but sets inactive status
- Production Environment: App Foundations API requires production endpoints
- GraphQL Endpoint:
https://qb.api.intuit.com/graphql - Required Scopes:
app-foundations.custom-field-definitions.read,app-foundations.custom-field-definitions - Authentication: OAuth 2.0 Bearer tokens
- CustomFieldDefinitionNode: Primary App Foundations model
- Legacy Compatibility: Automatic conversion to legacy
CustomFieldformat - Summary Views: Simplified
CustomFieldSummaryfor UI consumption - Extension Methods: Rich helper methods for association management and data conversion
- GraphQL Errors: Detailed error messages for schema validation
- Field Validation: Required field checking and data type validation
- Association Rules: Business rule enforcement for entity relationships
- Network Resilience: Retry logic and connection management
The application uses OAuth 2.0 with App Foundations scopes:
app-foundations.custom-field-definitions.read- Read custom field definitionsapp-foundations.custom-field-definitions- Full custom field definition managementcom.intuit.quickbooks.accounting- General QuickBooks access
- Automatic token refresh
- Session-based state management
- Secure token storage
- CSRF protection with state parameter
- Production/sandbox environment support
The QuickBooks CustomFields API provides complete Create, Read, Update, and Delete (CRUD) operations using the App Foundations GraphQL API.
Creates new custom fields with flexible association configurations using AppFoundations_CustomFieldDefinitionCreateInput.
label(String): Field name/labeldataType(String):STRING,NUMBER,DATEactive(Boolean): Field activation statusassociations(Array): Entity associations (optional, defaults to Transaction/SALE_INVOICE)
mutation CreateCustomFieldDefinition($input: AppFoundations_CustomFieldDefinitionCreateInput!) {
appFoundationsCreateCustomFieldDefinition(input: $input) {
id
label
dataType
active
associations { ... }
dropDownOptions { ... }
}
}Default Association (Backward Compatibility)
curl -X POST "http://localhost:5039/api/customfields" \
-H "Content-Type: application/json" \
-d '{
"name": "Default Field",
"type": "STRING"
}'Creates: Transaction/SALE_INVOICE association (default behavior)
Contact/Vendor Association
curl -X POST "http://localhost:5039/api/customfields" \
-H "Content-Type: application/json" \
-d '{
"name": "Vendor Contact Field",
"type": "STRING",
"associations": [
{
"associatedEntity": "/network/Contact",
"active": true,
"required": false,
"associationCondition": "INCLUDED",
"subAssociations": ["VENDOR"]
}
]
}'Multiple Transaction Types
curl -X POST "http://localhost:5039/api/customfields" \
-H "Content-Type: application/json" \
-d '{
"name": "Multi Transaction Field",
"type": "STRING",
"associations": [
{
"associatedEntity": "/transactions/Transaction",
"active": true,
"required": false,
"associationCondition": "INCLUDED",
"subAssociations": ["SALE_INVOICE", "SALE_ESTIMATE"]
}
]
}'- ✅ Single Entity Type: Each field can only associate with one main entity type
- ✅ Multiple Sub-Associations: Within an entity, multiple sub-types are supported
- ❌ Mixed Entity Types: Cannot combine Contact and Transaction associations in one field
⚠️ Mutual Exclusivity: Some sub-associations are mutually exclusive (API returns specific errors)
Retrieves custom field definitions with full association details.
GET /api/customfields/definitions- All definitions with associationsGET /api/customfields/summaries- Simplified summary viewGET /api/customfields/definitions/{id}- Specific field by IDGET /api/customfields- Legacy format (auto-converted)
query GetCustomFieldDefinitions {
appFoundationsCustomFieldDefinitions {
edges {
node {
id
legacyIDV2
label
dataType
active
associations { ... }
dropDownOptions { ... }
}
}
}
}# Get all definitions with associations
curl -X GET "http://localhost:5039/api/customfields/definitions"
# Get simplified summaries
curl -X GET "http://localhost:5039/api/customfields/summaries"
# Get specific field
curl -X GET "http://localhost:5039/api/customfields/definitions/{id}"
# Verify associations
curl -X GET "http://localhost:5039/api/customfields/summaries" | jq '.data[] | {name, associatedEntities, associations}'Updates existing custom fields using AppFoundations_CustomFieldDefinitionUpdateInput.
id(ID): Custom field identifierlegacyIDV2(ID): Legacy identifier (automatically retrieved)label(String): Current or new label (preserved if not provided)active(Boolean): Current or new status (preserved if not provided)dataType(String): Current or new data type (preserved if not provided)
mutation UpdateCustomFieldDefinition($input: AppFoundations_CustomFieldDefinitionUpdateInput!) {
appFoundationsUpdateCustomFieldDefinition(input: $input) {
id
legacyIDV2
label
dataType
active
associations { ... }
dropDownOptions { ... }
}
}Simple Name Update
curl -X PUT "http://localhost:5039/api/customfields/{id}" \
-H "Content-Type: application/json" \
-d '{"name": "Updated Field Name"}'Deactivate Field
curl -X PUT "http://localhost:5039/api/customfields/{id}" \
-H "Content-Type: application/json" \
-d '{"active": false}'Update Associations
curl -X PUT "http://localhost:5039/api/customfields/{id}" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Field",
"associations": [
{
"associatedEntity": "/network/Contact",
"active": true,
"required": true,
"associationCondition": "INCLUDED",
"subAssociations": ["CUSTOMER"]
}
]
}'Multiple Field Update
curl -X PUT "http://localhost:5039/api/customfields/{id}" \
-H "Content-Type: application/json" \
-d '{
"name": "Multi-Update Field",
"active": true,
"type": "STRING"
}'- ✅ Partial Updates: Only specified fields are updated
- ✅ Field Preservation: Non-specified fields retain current values
- ✅ Association Addition: New associations appear to be additive (preserves existing)
- ✅ Data Type Validation: Invalid types are rejected with clear errors
⚠️ Required Fields:legacyIDV2andlabelare required (auto-handled by API)
Deletes (deactivates) custom fields using the update schema as a soft delete.
Since the App Foundations API doesn't provide a direct delete mutation, deletion is implemented as an update operation that sets active: false. This is a safe, production-ready approach that preserves data integrity.
# Same as update, but sets active: false
mutation SoftDeleteCustomFieldDefinition($input: AppFoundations_CustomFieldDefinitionUpdateInput!) {
appFoundationsUpdateCustomFieldDefinition(input: $input) {
id
active # Will be false after soft delete
}
}Delete Custom Field
curl -X DELETE "http://localhost:5039/api/customfields/{id}"Verify Deletion
# Check field is now inactive
curl -X GET "http://localhost:5039/api/customfields/summaries" | jq '.data[] | select(.id == "{id}") | .active'- ✅ Soft Delete: Sets
active: falseinstead of permanent removal - ✅ Data Preservation: All field data, associations, and metadata preserved
- ✅ Audit Trail: Maintains history of deleted fields
- ✅ Reactivation: Fields can be restored by updating
active: true - ✅ Error Handling: Non-existent fields return proper 404 errors
- ✅ Association Preservation: Complex associations remain intact
- Data Safety: No permanent data loss
- Compliance: Maintains audit trails for regulatory requirements
- Reversibility: Accidental deletions can be undone
- Performance: Faster than hard deletes in production systems
- Integration: Works seamlessly with existing update infrastructure
The application provides complete invoice management with integrated custom field support, combining QuickBooks REST API v3 with App Foundations Custom Fields.
Creates new invoices with line items and custom field values.
Endpoint: POST /api/invoice/create
Required Fields:
customerId(string): QuickBooks customer IDinvoiceDate(string): Invoice date (YYYY-MM-DD)lineItems(array): At least one line item requireditemId(string): QuickBooks item/service IDdescription(string): Line item descriptionquantity(number): Item quantityunitPrice(number): Price per unitamount(number): Total amount (quantity × unitPrice)
Optional Fields:
dueDate(string): Payment due datecustomFields(array): Array of custom field valuesdefinitionId(string): Custom field DefinitionId (fromlegacyIDV2)name(string): Custom field name/labelvalue(string): Custom field valuedataType(string): Data type (STRING, NUMBER, DATE)
notes(string): Invoice notes/memo
Example:
# Create invoice with multiple custom fields
curl -X POST "http://localhost:5039/api/invoice/create" \
-H "Content-Type: application/json" \
-d '{
"customerId": "6",
"invoiceDate": "2025-12-01",
"dueDate": "2025-12-31",
"lineItems": [
{
"itemId": "9",
"description": "Consulting Services",
"quantity": 2,
"unitPrice": 150.00,
"amount": 300.00
},
{
"itemId": "8",
"description": "Additional Support",
"quantity": 1,
"unitPrice": 75.00,
"amount": 75.00
}
],
"customFields": [
{
"definitionId": "1000000010",
"name": "Project Code",
"value": "PRJ-2025-001",
"dataType": "STRING"
},
{
"definitionId": "1000000011",
"name": "Amount Field",
"value": "500",
"dataType": "NUMBER"
}
],
"notes": "Net 30 payment terms"
}'Response:
{
"success": true,
"data": {
"id": "62",
"docNumber": "1001",
"totalAmt": 375.00,
"customField": [
{
"definitionId": "1000000010",
"name": "Project Code",
"stringValue": "PRJ-2025-001"
},
{
"definitionId": "1000000011",
"name": "Amount Field",
"numberValue": 500
}
]
}
}Updates existing invoices with proper line item and custom field handling.
Endpoint: PUT /api/invoice/update
Required Fields:
invoiceId(string): Invoice ID to updatesyncToken(string): Current SyncToken (for optimistic locking)- All fields from CREATE (customerId, invoiceDate, lineItems, etc.)
Line Item Update Rules:
- Include
lineIdfor existing line items to update them - Omit
lineIdfor new line items to add them - Line items without
lineIdare treated as new additions
Example:
# Update invoice with multiple line items and custom fields
curl -X PUT "http://localhost:5039/api/invoice/update" \
-H "Content-Type: application/json" \
-d '{
"invoiceId": "62",
"syncToken": "14",
"customerId": "6",
"invoiceDate": "2025-12-01",
"dueDate": "2025-12-31",
"lineItems": [
{
"lineId": "16",
"itemId": "9",
"description": "Updated Consulting",
"quantity": 3,
"unitPrice": 150.00,
"amount": 450.00
},
{
"itemId": "8",
"description": "New Additional Service",
"quantity": 1,
"unitPrice": 100.00,
"amount": 100.00
}
],
"customFields": [
{
"definitionId": "1000000010",
"name": "Project Code",
"value": "PRJ-2025-002",
"dataType": "STRING"
}
],
"notes": "Updated terms"
}'Retrieves paginated invoice list with custom fields.
Endpoint: GET /api/invoice/list?page={page}&pageSize={pageSize}
Example:
# Get first 10 invoices
curl -X GET "http://localhost:5039/api/invoice/list?page=1&pageSize=10"
# Get next page
curl -X GET "http://localhost:5039/api/invoice/list?page=2&pageSize=10"Response:
{
"success": true,
"data": {
"invoices": [
{
"id": "62",
"docNumber": "1001",
"customerRef": {
"value": "6",
"name": "Acme Corp"
},
"txnDate": "2025-12-01",
"dueDate": "2025-12-31",
"totalAmt": 375.00,
"balance": 375.00,
"customField": [
{
"definitionId": "1000000010",
"name": "Project Code",
"stringValue": "PRJ-2025-001"
}
],
"line": [
{
"id": "16",
"amount": 300.00,
"description": "Consulting Services",
"salesItemLineDetail": {
"itemRef": { "value": "9" },
"qty": 2,
"unitPrice": 150.00
}
}
]
}
],
"page": 1,
"pageSize": 10,
"hasMore": false
}
}Get Customers:
curl -X GET "http://localhost:5039/api/customer/list"Get Items/Services:
curl -X GET "http://localhost:5039/api/item/list"- Create Custom Field for Invoices (App Foundations GraphQL):
curl -X POST "http://localhost:5039/api/customfields" \
-H "Content-Type: application/json" \
-d '{
"name": "Project Code",
"type": "STRING",
"associations": [{
"associatedEntity": "/transactions/Transaction",
"active": true,
"required": false,
"associationCondition": "INCLUDED",
"subAssociations": ["SALE_INVOICE"]
}]
}'Response includes legacyIDV2:
{
"success": true,
"data": {
"id": "udcf_1000000010",
"legacyIDV2": "1000000010",
"label": "Project Code",
"dataType": "STRING"
}
}- Use
legacyIDV2in Invoice (REST API v3):
curl -X POST "http://localhost:5039/api/invoice/create" \
-H "Content-Type: application/json" \
-d '{
"customerId": "6",
"invoiceDate": "2025-12-01",
"lineItems": [...],
"customFields": [
{
"definitionId": "1000000010",
"name": "Project Code",
"value": "PRJ-2025-001",
"dataType": "STRING"
}
]
}'The API request uses a customFields array with the following structure:
{
"customFields": [
{
"definitionId": "1000000010",
"name": "Project Code",
"value": "PRJ-2025-001",
"dataType": "STRING"
},
{
"definitionId": "1000000011",
"name": "Amount",
"value": "500",
"dataType": "NUMBER"
},
{
"definitionId": "1000000012",
"name": "Due Date",
"value": "2025-12-31",
"dataType": "DATE"
}
]
}- definitionId: Use
legacyIDV2from custom field definition - name: Custom field label/name
- value: The actual custom field value (always sent as string)
- dataType: Data type (STRING, NUMBER, DATE)
Note: All values are sent as StringValue to the QuickBooks API. QuickBooks returns them in the appropriate format (StringValue, NumberValue, or DateValue) based on the field's data type.
200 OK- Successful operation400 Bad Request- Invalid request data, GraphQL errors, or QuickBooks validation errors401 Unauthorized- Missing or invalid OAuth token404 Not Found- Custom field or invoice not found500 Internal Server Error- Server-side errors
Invalid ItemRef:
{
"error": "Invalid Reference Id : Line.SalesItemLineDetail.ItemRef"
}Solution: Ensure itemId exists in QuickBooks item list
Missing Line.Id for Updates:
"Only first line item updates, others are replaced"
Solution: Include lineId from original invoice for each existing line item
Custom Field Not Updating:
{
"error": "CustomField update failed"
}Solution: Ensure all required fields (definitionId, name, value, dataType) are included in each custom field object
SyncToken Mismatch:
{
"error": "Stale SyncToken"
}Solution: Fetch latest invoice data to get current syncToken
- Always check OAuth token validity before operations
- Use
GET /api/customfields/summariesto getlegacyIDV2for invoice custom fields - Include
lineIdwhen updating existing line items - Include all required fields (
definitionId,name,value,dataType) for each custom field - Test association combinations in development before production
- Monitor for GraphQL-specific error messages for debugging
- Use soft delete for production safety
The application includes a modern web interface for managing both custom fields and invoices.
Navigate to http://localhost:5039 in your browser to access the interactive web interface.
- OAuth authentication status
- Quick access to custom fields and invoices
- Real-time connection status with QuickBooks
- List View: Display all custom field definitions with filtering
- Create/Edit Forms: Interactive forms for managing custom fields
- Association Builder: Visual interface for entity associations
- Type Selection: Dropdown for data types (STRING, NUMBER, DATE)
- Real-time Validation: Client-side validation before submission
-
Invoice List: Paginated table showing all invoices
- Displays: Invoice #, Customer, Date, Due Date, Total, Balance, Custom Fields
- Edit and view capabilities for each invoice
- Pagination controls for large datasets
-
Create/Edit Invoice Modal:
- Customer Selection: Dropdown populated from QuickBooks customers
- Date Fields: Invoice date and due date pickers
- Line Items:
- Item/Service dropdown with auto-populated unit prices
- Description field for additional details
- Quantity and unit price inputs
- Real-time amount calculation
- Add/remove multiple line items
- Invoice Total: Auto-calculated sum of all line items
- Custom Fields:
- All SALE_INVOICE associated custom fields displayed automatically
- Dynamic input types based on field data type (text, number, date picker)
- Pre-populated on edit with existing values
- Support for multiple custom fields per invoice
- Notes: Optional memo field
- Real-time Calculations: Line item amounts and invoice totals update automatically
- Data Persistence: All changes sync with QuickBooks immediately
- Error Handling: User-friendly error messages for validation issues
- Responsive Design: Works on desktop and mobile devices
- Bootstrap 5: Modern, clean interface with icons
- Authenticate: Click "Connect to QuickBooks" button
- Create Custom Field:
- Navigate to Custom Fields tab
- Click "Create Custom Field"
- Enter name, select type, configure associations
- Save to QuickBooks
- Edit Custom Field:
- Click "Edit" button on a custom field row
- Modify name, status, or associations
- Save changes to QuickBooks
- Create Invoice:
- Navigate to Invoices tab
- Click "Create Invoice"
- Select customer from dropdown
- Add line items by selecting items/services
- Fill in values for any available custom fields (STRING, NUMBER, DATE)
- Click "Create Invoice"
- Edit Invoice:
- Click "Edit" button on invoice row
- Modify line items, dates, or custom field values
- All custom fields display with their current values pre-filled
- Click "Update Invoice"
Use Swagger UI or the Web UI for testing all API operations:
- Interactive Documentation: Explore all available endpoints
- OAuth Flow: Test the complete authentication flow with App Foundations scopes
- Custom Field Definition Operations: Create, read, update, delete custom field definitions
- Association Management: Test complex entity associations and sub-associations
- Invoice Operations: Test invoice CRUD with custom fields
- Legacy Compatibility: Test both new and legacy endpoint formats
- Real-time Responses: View API responses and error details
- Request Validation: Built-in parameter validation and examples
- Visual Interface: Test all operations through the web interface
- Real-time Validation: See validation errors immediately
- Live Data: Work with actual QuickBooks data
- User Experience: Test the complete user workflow
QuickBooks-CustomFields-API/
├── Controllers/
│ ├── CustomFieldsController.cs # Custom field CRUD operations
│ ├── InvoiceController.cs # Invoice management
│ ├── CustomerController.cs # Customer list endpoint
│ ├── ItemController.cs # Items/services endpoint
│ └── OAuthController.cs # OAuth authentication
├── Models/
│ ├── SharedModels.cs # Shared API models
│ ├── CustomFields.cs # Custom field models
│ └── Invoice.cs # Invoice and line item models
├── Services/
│ ├── ICustomFieldService.cs # Custom field service interface
│ ├── CustomFieldService.cs # GraphQL custom field operations
│ ├── ITokenManagerService.cs # Token management interface
│ └── TokenManagerService.cs # OAuth token handling
├── wwwroot/
│ ├── index.html # Main web UI
│ ├── js/
│ │ └── app.js # Frontend application logic
│ └── css/
│ └── styles.css # Custom styles
├── Program.cs # Application startup
└── appsettings.json # Configuration
IppDotNetSdkForQuickBooksApiV3(v14.7.0) - QuickBooks SDK for OAuthGraphQL.Client(v6.0.2) - GraphQL client for App Foundations APIGraphQL.Client.Serializer.Newtonsoft(v6.0.2) - JSON serialization
- App Foundations Integration: Native support for the latest QuickBooks API
- Smart Legacy Conversion: Automatic conversion between API formats
- Association Management: Handle complex entity relationships
- Dual ID Support: Handle both legacy encoded and V2 numeric IDs
- Extension Methods: Rich helper methods for data manipulation
The API includes comprehensive error handling:
- App Foundations GraphQL: Advanced error parsing and reporting
- OAuth Error Handling: User-friendly messages for authentication issues
- Association Validation: Errors for invalid entity associations
- Legacy Conversion: Graceful handling of data format differences
- Network and Timeout: Robust error handling for API calls
- OAuth 2.0 with App Foundations Scopes: Granular permission control
- State Parameter Validation: CSRF protection for OAuth flows
- Secure Token Storage: Automatic refresh with proper scope management
- Session-based State Management: Secure OAuth state handling
- Environment Isolation: Separate sandbox/production configurations
# Get OAuth authorization URL
curl -X GET "http://localhost:5039/api/oauth/authorize"
# Open returned URL in browser to authorize with QuickBooks
# Check authorization status
curl -X GET "http://localhost:5039/api/oauth/status"# Test default association
curl -X POST "http://localhost:5039/api/customfields" \
-H "Content-Type: application/json" \
-d '{"name": "Test Field", "type": "STRING"}'
# Test custom associations
curl -X POST "http://localhost:5039/api/customfields" \
-H "Content-Type: application/json" \
-d '{
"name": "Contact Field",
"type": "STRING",
"associations": [{
"associatedEntity": "/network/Contact",
"subAssociations": ["VENDOR"]
}]
}'
# Test association business rules (should fail)
curl -X POST "http://localhost:5039/api/customfields" \
-H "Content-Type: application/json" \
-d '{
"name": "Mixed Entity Field",
"type": "STRING",
"associations": [
{"associatedEntity": "/network/Contact", "subAssociations": ["CUSTOMER"]},
{"associatedEntity": "/transactions/Transaction", "subAssociations": ["SALE_INVOICE"]}
]
}'# Get all definitions
curl -X GET "http://localhost:5039/api/customfields/definitions"
# Get summaries with associations
curl -X GET "http://localhost:5039/api/customfields/summaries" | jq '.data[] | {name, active, associatedEntities}'
# Get specific field
curl -X GET "http://localhost:5039/api/customfields/definitions/{id}"
# Legacy compatibility
curl -X GET "http://localhost:5039/api/customfields"# Simple name update
curl -X PUT "http://localhost:5039/api/customfields/{id}" \
-H "Content-Type: application/json" \
-d '{"name": "Updated Name"}'
# Deactivate field
curl -X PUT "http://localhost:5039/api/customfields/{id}" \
-H "Content-Type: application/json" \
-d '{"active": false}'
# Update associations
curl -X PUT "http://localhost:5039/api/customfields/{id}" \
-H "Content-Type: application/json" \
-d '{
"associations": [{
"associatedEntity": "/network/Contact",
"subAssociations": ["CUSTOMER"]
}]
}'
# Test invalid data type (should fail)
curl -X PUT "http://localhost:5039/api/customfields/{id}" \
-H "Content-Type: application/json" \
-d '{"type": "INVALID_TYPE"}'# Soft delete (deactivate)
curl -X DELETE "http://localhost:5039/api/customfields/{id}"
# Verify deletion
curl -X GET "http://localhost:5039/api/customfields/summaries" | jq '.data[] | select(.id == "{id}") | .active'
# Test delete non-existent field (should fail)
curl -X DELETE "http://localhost:5039/api/customfields/invalid_id"Test Invoice Creation:
# Get customers and items first
curl -X GET "http://localhost:5039/api/customer/list"
curl -X GET "http://localhost:5039/api/item/list"
# Create invoice with custom field
curl -X POST "http://localhost:5039/api/invoice/create" \
-H "Content-Type: application/json" \
-d '{
"customerId": "6",
"invoiceDate": "2025-12-16",
"dueDate": "2026-01-15",
"lineItems": [
{
"itemId": "9",
"description": "Web Development",
"quantity": 10,
"unitPrice": 100.00,
"amount": 1000.00
}
],
"customFieldLegacyId": "1000000010",
"customFieldName": "Project Code",
"customFieldValue": "WEB-2025-001"
}'Test Invoice Update:
# Get invoice list first to get ID and SyncToken
curl -X GET "http://localhost:5039/api/invoice/list?page=1&pageSize=10"
# Update invoice (use ID and SyncToken from above)
curl -X PUT "http://localhost:5039/api/invoice/update" \
-H "Content-Type: application/json" \
-d '{
"invoiceId": "62",
"syncToken": "0",
"customerId": "6",
"invoiceDate": "2025-12-16",
"dueDate": "2026-01-15",
"lineItems": [
{
"lineId": "1",
"itemId": "9",
"description": "Updated Description",
"quantity": 15,
"unitPrice": 100.00,
"amount": 1500.00
},
{
"itemId": "8",
"description": "Additional Service",
"quantity": 5,
"unitPrice": 50.00,
"amount": 250.00
}
],
"customFieldLegacyId": "1000000010",
"customFieldName": "Project Code",
"customFieldValue": "WEB-2025-002"
}'Test Invoice with Custom Field Integration:
# 1. Create custom field for invoices
curl -X POST "http://localhost:5039/api/customfields" \
-H "Content-Type: application/json" \
-d '{
"name": "Department",
"type": "STRING",
"associations": [{
"associatedEntity": "/transactions/Transaction",
"active": true,
"subAssociations": ["SALE_INVOICE"]
}]
}'
# 2. Get legacyIDV2 from response (e.g., "1000000011")
# 3. Create invoice with new custom field
curl -X POST "http://localhost:5039/api/invoice/create" \
-H "Content-Type: application/json" \
-d '{
"customerId": "6",
"invoiceDate": "2025-12-16",
"lineItems": [{
"itemId": "9",
"description": "Service",
"quantity": 1,
"unitPrice": 500.00,
"amount": 500.00
}],
"customFieldLegacyId": "1000000011",
"customFieldName": "Department",
"customFieldValue": "Engineering"
}'
# 4. Verify custom field in invoice list
curl -X GET "http://localhost:5039/api/invoice/list?page=1&pageSize=10" | \
jq '.data.invoices[] | select(.customField != null) | {id, customField}'- Navigate to
http://localhost:5039(redirects to Swagger) - Use
/api/oauth/authorizeto initiate QuickBooks connection - Test all CRUD endpoints interactively:
- Custom Fields:
- POST
/api/customfields- Create with associations - GET
/api/customfields/definitions- Read with full data - PUT
/api/customfields/{id}- Update operations - DELETE
/api/customfields/{id}- Soft delete
- POST
- Invoices:
- GET
/api/invoice/list- List invoices with pagination - POST
/api/invoice/create- Create with line items and custom fields - PUT
/api/invoice/update- Update existing invoices
- GET
- Supporting:
- GET
/api/customer/list- Get customers for invoice creation - GET
/api/item/list- Get items/services for line items
- GET
- Custom Fields:
- Explore App Foundations endpoints vs REST API v3
- View detailed request/response examples with association data
- Navigate to
http://localhost:5039in your browser - Click "Connect to QuickBooks" to authenticate
- Test Custom Fields:
- Create a new custom field for SALE_INVOICE
- Verify it appears in the list
- Edit and update the field
- Test Invoices:
- Click "Create Invoice" button
- Select customer, add line items, add custom field value
- Save and verify in invoice list
- Click "Edit" on an invoice
- Modify line items and custom fields
- Update and verify changes
- Test Multi-Line Items:
- Create invoice with 3+ line items
- Edit and modify multiple line items
- Verify all line items update correctly
- Test Custom Field Integration:
- Create invoice with custom field
- Edit invoice and change custom field value
- Verify custom field updates in QuickBooks
For issues or questions:
- Check API Responses: Use Swagger UI to inspect detailed responses
- Verify App Configuration: Ensure
appsettings.jsonhas correct App Foundations scopes - OAuth Setup: Confirm app has
app-foundations.custom-field-definitionspermissions - Review Logs: Check application logs for detailed error messages
- Test Authentication: Use
/api/oauth/statusto verify token and scopes - Association Issues: Check entity associations in
/api/customfields/summaries - Legacy Compatibility: Compare responses between new and legacy endpoints