Accounting¶
The Accounting section of the Entity API covers GL journal entries, currency exchange rates, and customer form template assignments. These endpoints are used for financial integrations, multi-currency operations, and document configuration.
GL Entries¶
Endpoints¶
| Method | URL | Description |
|---|---|---|
GET | /entity/accounting/gl/ | Get all GL entries (ArrayOfGLEntry) |
POST | /entity/accounting/gl/ | Create a new GL entry |
GET | /entity/accounting/gl/{companyNo}_{glUid} | Get a specific GL entry |
PUT | /entity/accounting/gl/{companyNo}_{glUid} | Update a GL entry |
GET | /entity/accounting/gl/new | Get a blank GL entry with defaults |
GET | /entity/accounting/gl/ping | Health check |
GL Entry Fields¶
| Field | Type | Description |
|---|---|---|
company_no | string | Company identifier |
account_number | string | GL account number (chart of accounts) |
period | int | Accounting period (1–12 for calendar year) |
year_for_period | int | Fiscal year the period belongs to |
journal_id | string | Journal identifier (e.g., AP, AR, GJ) |
amount | decimal | Transaction amount in functional currency |
source | string | Source module or system (e.g., AP, AR, OE) |
description | string | Human-readable transaction description |
currency_id | string | Currency code for the transaction |
foreign_amount | decimal | Amount in foreign currency (if multi-currency) |
transaction_date | date | Date of the transaction |
transaction_number | string | Reference or document number |
approved | bool | Whether the entry has been approved |
job_id | string | Job or project ID for job-costing entries |
gl_uid | int | System-assigned unique identifier |
Functional vs. foreign currency
amount is always in the company's functional currency. foreign_amount is populated for multi-currency transactions and stores the value in the originating currency. currency_id identifies which currency foreign_amount is denominated in.
XML Example — GL Entry¶
<GLEntry xmlns="http://www.epicor.com/entity">
<company_no>01</company_no>
<gl_uid>98432</gl_uid>
<account_number>4000-00</account_number>
<period>11</period>
<year_for_period>2025</year_for_period>
<journal_id>AR</journal_id>
<amount>1250.00</amount>
<source>AR</source>
<description>Invoice 10043 - Acme Corp</description>
<currency_id>USD</currency_id>
<foreign_amount>0.00</foreign_amount>
<transaction_date>2025-11-15</transaction_date>
<transaction_number>INV-10043</transaction_number>
<approved>true</approved>
<job_id></job_id>
</GLEntry>
XML Example — Create GL Entry (Request Body)¶
<GLEntry xmlns="http://www.epicor.com/entity">
<company_no>01</company_no>
<account_number>6100-00</account_number>
<period>11</period>
<year_for_period>2025</year_for_period>
<journal_id>GJ</journal_id>
<amount>500.00</amount>
<source>GJ</source>
<description>Manual adjustment - Q4 accrual</description>
<currency_id>USD</currency_id>
<foreign_amount>0.00</foreign_amount>
<transaction_date>2025-11-30</transaction_date>
<transaction_number>ADJ-2025-1130</transaction_number>
<approved>false</approved>
</GLEntry>
Do not set gl_uid on creation
gl_uid is system-assigned. Do not include it in POST payloads. The response will contain the assigned value.
C# Example — Create GL Entry¶
public async Task<GLEntry> CreateGLEntryAsync(GLEntryCreateRequest request)
{
// Step 1: Fetch template
var templateResponse = await _httpClient.GetAsync($"{_baseUrl}/entity/accounting/gl/new");
templateResponse.EnsureSuccessStatusCode();
var templateXml = await templateResponse.Content.ReadAsStringAsync();
var serializer = new XmlSerializer(typeof(GLEntry));
using var templateReader = new StringReader(templateXml);
var template = (GLEntry)serializer.Deserialize(templateReader);
// Step 2: Populate fields
template.company_no = request.CompanyNo;
template.account_number = request.AccountNumber;
template.period = request.Period;
template.year_for_period = request.Year;
template.journal_id = request.JournalId;
template.amount = request.Amount;
template.source = request.Source;
template.description = request.Description;
template.currency_id = request.CurrencyId;
template.transaction_date = request.TransactionDate;
template.transaction_number = request.TransactionNumber;
template.approved = false;
// Step 3: Serialize and POST
using var writer = new StringWriter();
serializer.Serialize(writer, template);
var xml = writer.ToString();
var content = new StringContent(xml, Encoding.UTF8, "application/xml");
var response = await _httpClient.PostAsync($"{_baseUrl}/entity/accounting/gl/", content);
response.EnsureSuccessStatusCode();
var responseXml = await response.Content.ReadAsStringAsync();
using var reader = new StringReader(responseXml);
return (GLEntry)serializer.Deserialize(reader);
}
Exchange Rates¶
Endpoints¶
| Method | URL | Description |
|---|---|---|
GET | /entity/accounting/exchangerates/ | Get all exchange rates |
POST | /entity/accounting/exchangerates/ | Create a new exchange rate |
GET | /entity/accounting/exchangerates/{companyId}_{rateId} | Get a specific rate |
PUT | /entity/accounting/exchangerates/{companyId}_{rateId} | Update an exchange rate |
GET | /entity/accounting/exchangerates/new | Get a blank rate with defaults |
GET | /entity/accounting/exchangerates/ping | Health check |
Exchange Rate Fields¶
| Field | Type | Description |
|---|---|---|
currency_id | string | Currency code (e.g., EUR, CAD, GBP) |
exchange_rate | decimal | Rate relative to functional currency (e.g., 1.085 for EUR/USD) |
effective_date | date | Date from which this rate is effective |
rate_type | string | Rate category (e.g., SPOT, BUDGET, AVERAGE) |
company_id | string | Company identifier |
Effective date and rate history
P21 maintains exchange rate history. New rates do not overwrite old ones — each rate record is keyed by currency, rate type, and effective date. Create a new record for each rate change rather than updating existing records. This preserves audit history for period-end financial reporting.
XML Example — Exchange Rate¶
<ExchangeRate xmlns="http://www.epicor.com/entity">
<company_id>01</company_id>
<currency_id>EUR</currency_id>
<exchange_rate>1.0850</exchange_rate>
<effective_date>2025-11-01</effective_date>
<rate_type>SPOT</rate_type>
</ExchangeRate>
XML Example — Create Exchange Rate (Request Body)¶
<ExchangeRate xmlns="http://www.epicor.com/entity">
<company_id>01</company_id>
<currency_id>CAD</currency_id>
<exchange_rate>0.7320</exchange_rate>
<effective_date>2025-12-01</effective_date>
<rate_type>SPOT</rate_type>
</ExchangeRate>
C# Example — Create Exchange Rate¶
public async Task CreateExchangeRateAsync(string companyId, string currencyId,
decimal rate, DateTime effectiveDate, string rateType = "SPOT")
{
// Fetch template
var templateResponse = await _httpClient
.GetAsync($"{_baseUrl}/entity/accounting/exchangerates/new");
templateResponse.EnsureSuccessStatusCode();
var templateXml = await templateResponse.Content.ReadAsStringAsync();
var serializer = new XmlSerializer(typeof(ExchangeRate));
using var templateReader = new StringReader(templateXml);
var template = (ExchangeRate)serializer.Deserialize(templateReader);
template.company_id = companyId;
template.currency_id = currencyId;
template.exchange_rate = rate;
template.effective_date = effectiveDate;
template.rate_type = rateType;
using var writer = new StringWriter();
serializer.Serialize(writer, template);
var xml = writer.ToString();
var content = new StringContent(xml, Encoding.UTF8, "application/xml");
var response = await _httpClient
.PostAsync($"{_baseUrl}/entity/accounting/exchangerates/", content);
response.EnsureSuccessStatusCode();
}
Customer Form Templates¶
Endpoints¶
| Method | URL | Description |
|---|---|---|
GET | /entity/accounting/customerformtemplates/ | Get all customer form template assignments |
POST | /entity/accounting/customerformtemplates/ | Create a new template assignment |
GET | /entity/accounting/customerformtemplates/{companyId}_{templateId} | Get a specific assignment |
PUT | /entity/accounting/customerformtemplates/{companyId}_{templateId} | Update a template assignment |
GET | /entity/accounting/customerformtemplates/new | Get a blank template assignment |
GET | /entity/accounting/customerformtemplates/ping | Health check |
Customer form templates control which document layout (invoice format, statement format, etc.) is used for a given customer or customer group. This endpoint allows integration code to read and modify those assignments programmatically.
Use cases
Customer form template assignments are typically modified when onboarding new customers with custom document requirements, or when migrating document templates across environments. Most integrations do not need to modify these unless they are managing customer setup workflows end-to-end.
Notes and Common Pitfalls¶
GL entries and period locking
P21 supports period locking to prevent changes to closed accounting periods. Attempting to POST a GL entry into a locked period will return a validation error. Always verify the target period and year_for_period are open before creating entries.
Journal IDs and source codes
Use recognized journal IDs and source codes for your GL entries. Unrecognized values may pass validation but will cause GL entries to appear incorrectly in reports or be excluded from journal-specific queries. Consult your P21 chart of accounts configuration for valid values.
Approved flag on GL entries
The approved field controls whether a GL entry is included in period-end reporting. Entries created with approved=false are visible in the system but excluded from financial statements until approved. Use this for entries that require review before posting.