EnergyLink offers a rich set of Web APIs for sending or receiving Revenue & JIB statement data and automating file download workflows.
Contact your account representative about pricing to add this service to your EnergyLink subscription.
Web APIs can only be accessed via a Service Account user. A User ID and Password will be provided by EnergyLink Support during implementation. This is a service account only and cannot log into the website using a web browser. A separate user account can be provided if you need to log into the site for testing purposes. Each interface is available in both the Production and Demo environments, but a separate service account is used in each environment.
EnergyLink's REST APIs provide robust and flexible API access to transfer EnergyLink data into any database through custom developed automation.
Programmatically access EnergyLink Revenue & JIB datasets through REST APIs.
Data files may be uploaded to EnergyLink using one of several interfaces. Successful transmission does not indicate that the data was processed and validated successfully, which can take time. Emails can be configured to notify the sender whether or not a file was successfully processed.
A User ID and Password will be provided by EnergyLink Support. This is a service account only and cannot log into the website using a web browser. A separate user account can be provided if you need to log into the site for testing purposes. Each interface is available in both the Production and Demo environments but a separate service account is used in each.
REST APIs: Please see this page for details about EnergyLink's REST APIs.
RdWebApi: When using RdWebApi an instance of EnergyLinkClientUpload is always required, e.g.
using (var client = new EnergyLinkClientUpload(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
// Your API calls go here. See examples below.
}
var result = await client.UploadJibXmlAsync(
stream, //e.g. MemoryStream, FileStream, OracleBlob
"file.xml",
new DateTime(2018, 11, 30, 12, 0, 0, DateTimeKind.Local)
);
var result = await client.UploadJibCsvAsync(
stream, //e.g. MemoryStream, FileStream, OracleBlob
"file.xml",
new DateTime(2018, 11, 30, 12, 0, 0, DateTimeKind.Local)
);
var result = await client.UploadRevenueAsync(
stream, //e.g. MemoryStream, FileStream, OracleBlob
"file.xml",
new DateTime(2018, 11, 30, 12, 0, 0, DateTimeKind.Local),
"ORG1",
"EFT" // Some file formats include Org Num and Payment Type in the file, otherwise include it here
);
var result = await client.UploadVoidCheckAsync(
stream, //e.g. MemoryStream, FileStream, OracleBlob
"file.xml"
);
var result = await client.UploadDeskAuditXmlAsync(
stream, //e.g. MemoryStream, FileStream, OracleBlob
"file.xml"
);
var result = await client.UploadArBalancesAsync(
stream, //e.g. MemoryStream, FileStream, OracleBlob
"file.xml"
);
EnergyLink is now offering Web APIs to retrieve operated AR information.
A User ID and Password will be provided by EnergyLink Support. This is a service account only and cannot log into the website using a web browser. A separate user account can be provided if you need to log into the site for testing purposes. Each interface is available in both the Production and Demo environments but a separate service account is used in each.
REST APIs: Please see this page for details about EnergyLink's REST APIs.
Queries the results of the Org Summary tab on the existing AR Subledger XLS Management report.
using (var client = new EnergyLinkClientOp(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
var result = client.GetManagementReportOrgSummaryAsync(
new DateTime(2023, 11, 1),
new DateTime(2024, 8, 1)
);
}
All values are automatically deserialized into .Net classes within the ArManagementDataElement class. Happy coding!
The following methods may be used to move 'Created' inquiries (including ACH and COA) and their related files from EnergyLink to the client's environment. Upon download the status will change to 'Downloaded' and the inquiry will no longer be returned in the 'Inquiries for Download' queries. An inquiry can be set from 'Downloaded' back to 'Created' by manually re-creating it via the website.
By default users must create inquiries using the EnergyLink website.
It is possible to have custom fields added to the response as needed. This will have to be set up by the EnergyLink support team.
A User ID and Password will be provided by EnergyLink Support. This is a service account only and cannot log into the website using a web browser. A separate user account can be provided if you need to log into the site for testing purposes. Each interface is available in both the Production and Demo environments but a separate service account is used in each.
REST APIs: Please see this page for details about EnergyLink's REST APIs.
Returns the unprocessed owner inquiries for changes to their electronic payment information. When isTesting=False, receiving these will mark the inquiry as processed on EnergyLink
using (var client = new EnergyLinkClientInquiry(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
var result = await client.AchExport;
}
Returns the unprocessed owner inquiries for changes to their address information. When isTesting=False, receiving these will mark the inquiry as processed on EnergyLink
using (var client = new EnergyLinkClientInquiry(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
var result = await client.GetCoaExport;
}
var result = await client.UploadInquiryOwnerInfoCsvAsync(
stream, //e.g. MemoryStream, FileStream, OracleBlob
"file.csv.zip"
);
var items = new List<AssignVoucherReferencesRequestItem>
{
new AssignVoucherReferencesRequestItem
{
InquiryId = 123,
CloseInquiryMessage = "Some message",
}
};
var result = await client.UploadInquiryCloseRequestJsonAsync(items);
var resultContent = await result.Content.ReadAsStringAsync(); //resultContent is a list of InquiryIds and the message
var items = new List<AssignVoucherReferencesRequestItem>
{
new AssignVoucherReferencesRequestItem
{
InquiryId = 123,
CloseInquiryMessage = "Some message",
}
};
var result = await client.UploadInquiryCloseRequestJsonAsync(items);
var resultContent = await result.Content.ReadAsStringAsync(); //resultContent is a list of InquiryIds and the message
The following methods may be used to move 'Created' vouchers and their related files from EnergyLink to the client's environment. Upon download the status will change to 'Downloaded' and the voucher will no longer be returned in the 'Vouchers for Download' queries. A voucher can be set from 'Downloaded' back to 'Created' by manually re-creating it via the website.
By default users must create vouchers using the EnergyLink website. In some scenarios vouchers can be created automatically as data is received (e.g. if only operator coding is required).
Numerous additional options exist, depending on file format (e.g. CDEX). Please contact us to discuss your specific requirements.
For information about automating the steps to process an invoice/check and create the voucher (e.g. using RPA software), see the Non-Operated Workflow section.
EnergyLink will transmit files to a location provided by the client as they become available.
Clients may connect to the EnergyLink FTP site periodically to list and download new files.
A User ID and Password will be provided by EnergyLink Support. This is a service account only and cannot log into the website using a web browser. A separate user account can be provided if you need to log into the site for testing purposes. Each interface is available in both the Production and Demo environments but a separate service account is used in each.
A User ID and Password will be provided by EnergyLink Support. This is a service account only and cannot log into the website using a web browser. A separate user account can be provided if you need to log into the site for testing purposes. Each interface is available in both the Production and Demo environments but a separate service account is used in each.
REST APIs: Please see this page for details about EnergyLink's REST APIs.
using (var client = new EnergyLinkClientVoucher(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
var result = await client.GetInvoiceVouchersForDownloadAsync();
}
All values are automatically deserialized into .Net classes within the VouchersElement class. Happy coding!
using (var client = new EnergyLinkClientVoucher(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
var result = await client.GetPaymentVouchersForDownloadAsync();
}
All values are automatically deserialized into .Net classes within the VouchersElement class. Happy coding!
Usually you will need to call GetInvoiceVouchersForDownload first to get the Voucher Id.
using (var client = new EnergyLinkClientVoucher(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
var result = await client.GetVoucherFileStreamAsync(123);
}
All values are automatically deserialized into .Net classes within the VoucherFile class. Happy coding!
Usually you will need to call GetInvoiceVouchersForDownload first to get the Voucher Id.
Once EnergyLink enables the PDF option, the PDF backup is automatically created with each voucher. The GetVoucherReports Web API call will fetch these reports.
using (var client = new EnergyLinkClientVoucher(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
var result = await client.GetVoucherReportsAsync(123);
}
All values are automatically deserialized into .Net classes within the VoucherFile class. Happy coding!
EnergyLink offers a rich set of Web APIs for retrieving information about Invoices and/or Checks.
A User ID and Password will be provided by EnergyLink Support. This is a service account only and cannot log into the website using a web browser. A separate user account can be provided if you need to log into the site for testing purposes. Each interface is available in both the Production and Demo environments but a separate service account is used in each.
Parts of the data returned by these APIs are used by the EnergyLink Workflow APIs. For Example: Invoice ID, or Check Id.
See Non-Operated Workflow
for details.
Queries high-level information about multiple invoices. For statement-level and detail-level information see Invoice.
using (var client = new EnergyLinkClientNonOp(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
var nonOpInvoices = await client.GetInvoiceSearchAsync(
Enums.DateSearchType.RECEIVED_DATE,
new DateTime(2026, 3, 17, 12, 0, 0, DateTimeKind.Local),
invoiceType: new[] { Enums.InvoiceTypes.JIB }
);
}
All values from the NonOpInvoices XML schema are automatically deserialized into .Net classes within the NonOpInvoicesElement class. Happy coding!
USA only: Queries high-level information about multiple checks. For property-level and detail-level information see Revenue.
using (var client = new EnergyLinkClientNonOp(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
var nonOpRevenue = await client.GetRevenueSearchAsync(
Enums.DateSearchType.RECEIVED_DATE,
new DateTime(2026, 3, 17, 12, 0, 0, DateTimeKind.Local)
);
}
All values from the NonOpRevenue XML schema are automatically deserialized into .Net classes within the NonOpRevenueElement class. Happy coding!
Canada only: Queries high-level information about multiple royalties. For property-level and detail-level information see Royalty.
using (var client = new EnergyLinkClientNonOp(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
var nonOpRoyalties = await client.GetRoyaltySearchAsync(
Enums.DateSearchType.RECEIVED_DATE,
new DateTime(2026, 3, 17, 12, 0, 0, DateTimeKind.Local)
);
}
All values from the NonOpRoyalties XML schema are automatically deserialized into .Net classes within the NonOpRoyaltiesElement class. Happy coding!
Queries detailed information about a single invoice. For high-level information on multiple invoices see InvoiceSearch.
using (var client = new EnergyLinkClientNonOp(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
var nonOpInvoices = await client.GetInvoiceAsync(
123456,
Enums.InvoiceSummarization.Detail
);
}
All values from the NonOpInvoices XML schema are automatically deserialized into .Net classes within the NonOpInvoicesElement class. Happy coding!
USA only: Queries detailed information about a single check. For high-level information on multiple checks see RevenueSearch.
using (var client = new EnergyLinkClientNonOp(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
var nonOpRevenue = await client.GetRevenueAsync(
123456,
Enums.InvoiceSummarization.Detail
);
}
All values from the NonOpRevenue XML schema are automatically deserialized into .Net classes within the NonOpRevenueElement class. Happy coding!
Canada only: Queries detailed information about a single royalty. For high-level information on multiple royalties see RoyaltySearch.
using (var client = new EnergyLinkClientNonOp(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
var nonOpRoyalty = await client.GetRoyaltyAsync(
123456,
Enums.InvoiceSummarization.Detail
);
}
All values from the NonOpRoyalties XML schema are automatically deserialized into .Net classes within the NonOpRoyaltiesElement class. Happy coding!
EnergyLink offers a rich set of Web APIs for retrieving information and automating workflow. The RdWebApi package is strongly recommended for the latter.
A User ID and Password will be provided by EnergyLink Support. This is a service account only and cannot log into the website using a web browser. A separate user account can be provided if you need to log into the site for testing purposes. Each interface is available in both the Production and Demo environments but a separate service account is used in each.
The first step is to search for documents and download their details. See Non-Operated Reporting for details. EnergyLink primary key values will be included with this data - store and refer to these keys when making subsequent Web API calls. See the Samples section for examples of multiple Web APIs used together to complete more complex tasks.
When using RdWebApi an instance of EnergyLinkClientNonOp is always required, e.g.
using (var client = new EnergyLinkClientNonOp(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
// Your API calls go here. See examples below.
}
Identifies all issues preventing this invoice from being vouchered (e.g. missing approval).
var result = await client.ValidateInvoiceAsync(123);
var afeElements = new List<AfeElement>
{
new AfeElement {
AfeId = 123,
PrtAfeNumber = "AFE123", // Optional
PrtCostCenterCode = "AFECC123", // Optional
PrtAfeActionCode = Enums.AfeActionCodes.DisputeSold // Optional
}
};
var result = await client.CreateAfeXrefsAsync(afeElements);
var ccElements = new List<CostCenterElement>
{
new CostCenterElement {
CcId = 123,
PrtAfeNumber = "AFE123", // Optional
PrtCostCenterCode = "CC123", // Optional
PrtCostCenterActionCode = Enums.CcActionCodes.DisputeSold // Optional
}
};
var result = await client.CreateCcXrefsAsync(ccElements);
var afeElements = new List<AfeElement>
{
new AfeElement {
AfeId = 123,
AfeXrefId = 456,
PrtAfeNumber = "AFE123", // Optional
PrtCostCenterCode = "AFECC123", // Optional
PrtAfeActionCode = Enums.AfeActionCodes.DisputeSold // Optional
}
};
var result = await client.UpdateAfeXrefsAsync(afeElements);
var ccElements = new List<CostCenterElement>
{
new CostCenterElement {
CcId = 123,
CcXrefId = 456,
PrtAfeNumber = "AFE123", // Optional
PrtCostCenterCode = "CC123", // Optional
PrtCostCenterActionCode = Enums.CcActionCodes.DisputeSold // Optional
}
};
var result = await client.UpdateCcXrefsAsync(ccElements);
var statementDispute = new StatementDisputeElement
{
DisputeReason = Enums.DisputeReasonCode.Sold,
DisputeComment = "Our records indicate that we have sold this property",
DisputeEntireStatement = true
};
var result = await EnergyLinkClient.UpdateStatementDisputesAsync(12345, statementDispute);
var invoiceIds = new List<long> { 12345, 12346 };
var result = await client.ApproveInvoicesAsync(invoiceIds);
//optional. you can pass a null as the 2nd parameter to approve the entire statement
var statementApproval = new StatementApprovalElement
{
ExceptDetailIds = new List<long> { 12346 }
};
var result = await client.ApproveStatementAsync(12345, statementApproval);
var items = new List<AssignVoucherReferencesRequestItem>
{
new AssignVoucherReferencesRequestItem
{
InvoiceId = 123,
VoucherReference = "VCH123",
VoucherAccountingMonth = new DateTime(2018, 11, 30, 12, 0, 0, DateTimeKind.Local)
}
};
var result = await client.AssignVoucherReferencesAsync(items);
var resultContent = await result.Content.ReadAsStringAsync();
//resultContent is a list of InvoiceIds and their new VoucherIds
var responseItems = JsonConvert.DeserializeObject<List<EnergyLinkClientNonOp.AssignVoucherReferencesResponseItem>>(content);
Identifies all issues preventing this voucher from being created (e.g. missing approval).
var result = await client.ValidateVoucherAsync(123);
Attempt to queue the voucher for creation. When finished the file will be available via the methods outlined under Non-Operated File Downloads.
var result = await client.QueueVoucherAsync(123);
The following sample searches for invoices received within the past 2 days and attempts to:
InvoiceProcessingBot, then run ProcessInvoices().
See Non-Operated File Downloads for options to automatically download the voucher file and PDF backup.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using RedDog.RdWebApi.NonOp;
namespace RedDog.RdWebApi.Samples
{
public abstract class InvoiceProcessingBot
{
public abstract EnergyLinkEnvironment EnergyLinkEnvironment { get; }
public abstract string EnergyLinkLoginName { get; }
public abstract string EnergyLinkPassword { get; }
protected virtual EnergyLinkClientNonOp EnergyLinkClient { get; set; }
public abstract bool DoApprovals { get; }
public abstract bool AssignVoucherRefIfApproved { get; }
public abstract bool QueueVoucherIfVoucherRefAssigned { get; }
public virtual async Task ProcessInvoices()
{
using (EnergyLinkClient ?? (EnergyLinkClient = new EnergyLinkClientNonOp(EnergyLinkEnvironment, EnergyLinkLoginName, EnergyLinkPassword)))
{
Log("Getting Invoices", 1);
var invoiceSearch = await GetInvoiceSearch();
foreach (var invoice in invoiceSearch.Invoices)
{
var invoiceWithDetail = (await EnergyLinkClient.GetInvoiceAsync(invoice.InvoiceId, Enums.InvoiceSummarization.Detail)).Invoices[0];
await ProcessInvoice(invoiceWithDetail);
await ApproveAndVoucherInvoice(invoiceWithDetail);
}
}
}
protected virtual async Task<NonOpInvoicesElement> GetInvoiceSearch()
{
using (var client = new EnergyLinkClientNonOp(EnergyLinkEnvironment, EnergyLinkLoginName, EnergyLinkPassword))
{
//get a list of JIBs received in the past 2 days still in Received or Viewed status
return await client.GetInvoiceSearchAsync(
Enums.DateSearchType.RECEIVED_DATE,
DateTime.Today.AddDays(-2),
invoiceType: new []{ Enums.InvoiceTypes.JIB },
status: new []{ Enums.InvoiceStatusCodes.Received, Enums.InvoiceStatusCodes.Viewed }
);
}
}
#region Process Invoice
protected virtual async Task ProcessInvoice(InvoiceElement invoice)
{
Log($"Processing {invoice.Operator.OrgName} Invoice {invoice.InvoiceNumber}", 1);
var newAfeXrefs = new Dictionary<long, AfeElement>();
var newCcXrefs = new Dictionary<long, CostCenterElement>();
foreach (var statement in invoice.Statements)
{
await ProcessStatement(statement);
if (!statement.AcceptedPartnerAmount.HasValue && !statement.AcceptedGstAmount.HasValue && !statement.AcceptedCashCallAmount.HasValue)
{
//Mapping is not required for fully disputed statements
continue;
}
if (statement.Afe != null && !statement.Afe.AfeXrefId.HasValue)
{
if (TryMapAfe(statement.Afe) && !string.IsNullOrWhiteSpace(statement.Afe.PrtAfeNumber))
{
newAfeXrefs.Add(statement.Afe.AfeId, statement.Afe);
}
}
if (statement.CostCenter != null && !statement.CostCenter.CcXrefId.HasValue)
{
if (TryMapCostCenter(statement.CostCenter) && !string.IsNullOrWhiteSpace(statement.CostCenter.PrtCostCenterCode))
{
newCcXrefs.Add(statement.CostCenter.CcId, statement.CostCenter);
}
}
}
if (newAfeXrefs.Count > 0)
{
foreach (var newAfeXref in newAfeXrefs.Values)
{
Log($"OpAfeNumber '{newAfeXref.OpAfeNumber}' will be mapped to '{newAfeXref.PrtAfeNumber}'");
}
var result = await EnergyLinkClient.CreateAfeXrefsAsync(newAfeXrefs.Values.ToList());
if (!result.IsSuccessStatusCode) throw new InvalidOperationException($"Error mapping {newAfeXrefs.Count} AFEs: {result.ReasonPhrase}");
Log($"Mapped {newAfeXrefs.Count} AFEs");
}
if (newCcXrefs.Count > 0)
{
foreach (var newCcXref in newCcXrefs.Values)
{
Log($"OpCostCenterCode '{newCcXref.OpCostCenterCode}' will be mapped to '{newCcXref.PrtCostCenterCode}'");
}
var result = await EnergyLinkClient.CreateCcXrefsAsync(newCcXrefs.Values.ToList());
if (!result.IsSuccessStatusCode) throw new InvalidOperationException($"Error mapping {newCcXrefs.Count} CostCenters: {result.ReasonPhrase}");
Log($"Mapped {newCcXrefs.Count} CostCenters");
}
}
protected virtual bool TryMapAfe(AfeElement afe)
{
//Implement logic here to set the PrtAfeNumber (and optionally the PrtCostCenterCode, PrtAfeActionCode)
return false;
}
protected virtual bool TryMapCostCenter(CostCenterElement cc)
{
//Implement logic here to set the PrtCostCenterCode (and optionally the PrtAfeNumber, PrtCostCenterActionCode)
return false;
}
protected virtual async Task ProcessStatement(StatementElement statement)
{
//Hint: Clients using Desk Audit can easily set their 'Sold' and 'No Interest'
//property flags online based on data from the land system
switch (statement.CostCenter?.PrtCostCenterActionCode)
{
case Enums.CcActionCodes.DisputeNoInterest:
if (await DisputeNoInterest(statement)) return;
break;
case Enums.CcActionCodes.DisputeSold:
if (await DisputeSold(statement)) return;
break;
}
//If we are not rejecting the property outright then continue onto possible detail-level disputes.
//Note: There can only be one Dispute Reason and Dispute Comment
// per statement so pick the "most important" one that applies.
// In this case we assume that DOI > Overhead > Self-Insured
var statementDispute = new StatementDisputeElement
{
DetailDisputes = new List<DetailDisputeElement>()
};
foreach (var detail in statement.Details)
{
if (DisputeDoi(statement, detail, out var correctDoi))
{
if (!statementDispute.DisputeReason.HasValue)
{
statementDispute.DisputeReason = Enums.DisputeReasonCode.Wrong_DOI;
statementDispute.DisputeComment = $"Our records indicate that this DOI should be {correctDoi}.";
}
statementDispute.DetailDisputes.Add(new DetailDisputeElement { DetailId = 123, AcceptedPartnerAmount = null });
}
else if (IsOverhead(detail) && DisputeOverhead(statement, detail, out var correctOverhead))
{
if (!statementDispute.DisputeReason.HasValue)
{
statementDispute.DisputeReason = Enums.DisputeReasonCode.Overhead_Fee;
statementDispute.DisputeComment = $"Our records indicate that the overhead rate should be {correctOverhead}.";
}
statementDispute.DetailDisputes.Add(new DetailDisputeElement { DetailId = 123, AcceptedPartnerAmount = null });
}
else if (IsInsurance(detail) && DisputeInsurance(statement))
{
if (!statementDispute.DisputeReason.HasValue)
{
statementDispute.DisputeReason = Enums.DisputeReasonCode.Self_Insured;
statementDispute.DisputeComment = "Our records indicate that this property is self-insured.";
}
statementDispute.DetailDisputes.Add(new DetailDisputeElement { DetailId = 123, AcceptedPartnerAmount = null });
}
}
if (statementDispute.DisputeReason.HasValue && statementDispute.DetailDisputes.Count > 0)
{
var result = await EnergyLinkClient.UpdateStatementDisputesAsync(statement.StatementId, statementDispute);
if (!result.IsSuccessStatusCode)
throw new InvalidOperationException($"Error updating disputes on StatementId {statement.StatementId}: {result.ReasonPhrase}");
}
await Task.FromResult(0);
}
protected virtual async Task<bool> DisputeNoInterest(StatementElement statement)
{
var statementDispute = new StatementDisputeElement
{
DisputeReason = Enums.DisputeReasonCode.NoInterest,
DisputeComment = "Our records indicate that we have no interest in this property",
DisputeEntireStatement = true
};
var result = await EnergyLinkClient.UpdateStatementDisputesAsync(statement.StatementId, statementDispute);
if (!result.IsSuccessStatusCode)
throw new InvalidOperationException($"Error updating disputes on StatementId {statement.StatementId}: {result.ReasonPhrase}");
return true;
}
protected virtual async Task<bool> DisputeSold(StatementElement statement)
{
var statementDispute = new StatementDisputeElement
{
DisputeReason = Enums.DisputeReasonCode.Sold,
DisputeComment = "Our records indicate that we have sold this property",
DisputeEntireStatement = true
};
var result = await EnergyLinkClient.UpdateStatementDisputesAsync(statement.StatementId, statementDispute);
if (!result.IsSuccessStatusCode)
throw new InvalidOperationException($"Error updating disputes on StatementId {statement.StatementId}: {result.ReasonPhrase}");
return true;
}
protected virtual bool DisputeDoi(StatementElement statement, DetailElement detail, out decimal? correctDoi)
{
//Implement logic here to decide if this DOI should be disputed (e.g. if it does not match your land system)
//Hint: Clients using Desk Audit can easily get this info from the EnergyLink API calls included in this package
correctDoi = 50;
return false;
}
protected virtual bool IsOverhead(DetailElement detail)
{
//Hint: Compare detail.PrtMajorAccountCode and detail.PrtMinorAccountCode to your GL account(s) for overhead
return detail.EnergyLinkMinorAccountDesc.IndexOf("OVERHEAD", StringComparison.InvariantCultureIgnoreCase) >= 0;
}
protected virtual bool DisputeOverhead(StatementElement statement, DetailElement detail, out decimal? correctOverhead)
{
//Implement logic here to decide if this overhead line should be disputed (e.g. if it does not match your land system)
//Hint: Clients using Desk Audit can easily get this info from the EnergyLink API calls included in this package
correctOverhead = 150;
return false;
}
protected virtual bool IsInsurance(DetailElement detail)
{
//Hint: Compare detail.PrtMajorAccountCode and detail.PrtMinorAccountCode to your GL account(s) for insurance
return detail.EnergyLinkMinorAccountDesc.IndexOf("INSURANCE", StringComparison.InvariantCultureIgnoreCase) >= 0;
}
protected virtual bool DisputeInsurance(StatementElement statement)
{
//Implement logic here to decide if insurance should be disputed on this property (e.g. if it is self-insured)
return false;
}
#endregion
#region Approve and Voucher
protected virtual async Task ApproveAndVoucherInvoice(InvoiceElement invoice)
{
var validateResult = await EnergyLinkClient.ValidateInvoiceAsync(invoice.InvoiceId);
if (validateResult.Validations.Any(x => x.Key != Enums.Validations.APPROVAL_REQD))
{
Log($"Validation Summary:\r\n{validateResult.ValidationSummary}");
Log($"Invoice {invoice.InvoiceNumber} has outstanding validations - cannot continue.");
return;
}
if (!invoice.IsApproved)
{
if (!DoApprovals)
{
Log("Not configured to approve invoices - done with this invoice.");
return;
}
if (ApproveInvoice(invoice))
{
var result = await EnergyLinkClient.ApproveInvoicesAsync(new List<long> {invoice.InvoiceId});
if (!result.IsSuccessStatusCode) throw new InvalidOperationException($"Error approving InvoiceId {invoice.InvoiceId}: {result.ReasonPhrase}");
Log($"Invoice {invoice.InvoiceNumber} approved.");
}
else
{
Log("Did not meet criteria for approval - done with this invoice.");
return;
}
}
if (validateResult.OtherInvoicesWithValidations?.Count > 0)
{
Log($"Invoice {invoice.InvoiceNumber} is ready for download but InvoiceIds {string.Join(",", validateResult.OtherInvoicesWithValidations.Select(x => x.ToString()))} share the voucher and still have validations.");
return;
}
if (!invoice.ActiveVoucherId.HasValue)
{
if (!AssignVoucherRefIfApproved)
{
Log("Not configured to assign voucher references - done with this invoice.");
return;
}
var voucher = AssignVoucher(invoice);
var result = await EnergyLinkClient.AssignVoucherReferencesAsync(new List<EnergyLinkClientNonOp.AssignVoucherReferencesRequestItem> {voucher});
if (!result.IsSuccessStatusCode) throw new InvalidOperationException($"Error assigning voucher reference to InvoiceId {invoice.InvoiceId}: {result.ReasonPhrase}");
var content = await result.Content.ReadAsStringAsync();
var response = JsonConvert.DeserializeObject<List<EnergyLinkClientNonOp.AssignVoucherReferencesResponseItem>>(content);
if (response?.Count != 1 || !response[0].VoucherId.HasValue) throw new InvalidOperationException($"Error assigning voucher reference to InvoiceId {invoice.InvoiceId}: No VoucherId was returned.");
Log($"Voucher Reference {invoice.InvoiceNumber} assigned.");
if (!QueueVoucherIfVoucherRefAssigned)
{
Log("Not configured to queue vouchers - done with this invoice.");
return;
}
var voucherId = response[0].VoucherId.Value;
var queueResult = await EnergyLinkClient.QueueVoucherAsync(voucherId);
if (!queueResult.IsSuccessStatusCode) throw new InvalidOperationException($"Error queueing VoucherId {voucherId}: {result.ReasonPhrase}");
Log($"Voucher {voucher.VoucherReference} queued for creation.");
}
}
protected virtual bool ApproveInvoice(InvoiceElement invoice)
{
//Implement logic here to decide whether to approve an invoice for download if the validations are satisfied, e.g.:
var invoiceAmount = (invoice.OriginalPartnerAmount ?? 0) + (invoice.OriginalGstAmount ?? 0) + (invoice.OriginalCashCallAmount ?? 0);
return Math.Abs(invoiceAmount) <= 100;
}
protected virtual EnergyLinkClientNonOp.AssignVoucherReferencesRequestItem AssignVoucher(InvoiceElement invoice)
{
//Implement logic here to assign the voucher reference and accounting month, e.g.:
var voucher = new EnergyLinkClientNonOp.AssignVoucherReferencesRequestItem
{
InvoiceId = invoice.InvoiceId,
VoucherReference = $"{invoice.InvoiceNumber}.{invoice.InvoiceId}",
VoucherAccountingMonth = DateTime.Today
};
//Note: appending InvoiceId will ensure uniqueness if using one invoice per voucher
return voucher;
}
#endregion
protected virtual void Log(string message, int tabs = 2)
{
Trace.WriteLine($"{DateTime.Now:yyyy-MM-dd hh:mm:ss}{string.Concat(Enumerable.Repeat("\t", tabs))}{message}");
}
}
}
Nuget allows .Net code to be shared between applications. When it comes to EnergyLink APIs the need arose internally to use a Nuget package to help manage API calls between EnergyLink and those applications. We also used the package to write automated tests for those interactions.
With many EnergyLink clients, vendors and 3rd parties now looking to use Web APIs, we decided to take it a step further and offer RdWebApi externally. We see many benefits to this approach:
The package is available via a public Nuget repository for any full-service client. Please contact us to get the link. We would be happy to discuss your project and help you find the best solution.
In scenarios where Nuget is unsupported and a .DLL file is required, the .DLL must be extracted from the .Nuget file. .Nuget files are zip files so just change the extension to ".zip" and browse the file for the appropriate .DLL.
Each category of APIs (Operated File Uploads, Non-Operated File Downloads, Non-Operated Automation) has a different 'EnergyLinkClient' class. Once this has been identified, most Web API calls take two lines of code, e.g.:
using (var client = new EnergyLinkClientUpload(
environment: EnergyLinkEnvironment.Test,
loginName: "USERID",
password: "PASSWORD")
)
{
var result = await client.UploadJibXmlAsync(inputStream, "Invoices.xml");
}
Examples are provided for each Web API call elsewhere on this page
Some clients have expressed an interest in calling Web APIs from commercially available RPA software. Many of these products can integrate with the RdWebApi package - details for some of these products are noted below. Please contact us if you'd like to discuss your RPA project.
While Nuget packages cannot be imported directly, .Net DLLs can be used by custom metabots. For information see this example video.
Blue Prism can reference a custom .Net DLL but we have not yet worked with a client using this product.
MuleSoft has a .Net Connector for importing .Net DLLs. RdWebApi could be imported directly, or a custom project can be created which references RdWebApi.
Selenium is used purely for web browser automation. Web APIs are a much better approach (particularly if the RdWebApi package is an option).
Great news! UI Path is able to import Nuget packages and works with RdWebApi. Using Windows Workflow Foundation Activities, each Web API call can be integrated right into your project. You can use our activities included in the package, or reference RdWebApi from your own .Net project and create your own activities. Please contact us for a detailed explanation on how to set this up inside UI Path.
This is currently used by Desk Audit clients.
Ever wonder why EnergyLink doesn't just interact directly with your accounting and land systems to transfer data? The main reason is a security one - industry standard practice is to deny all incoming connections unless they match a very specific rule. These rules are tedious to manage as your company and its infrastructure evolve.
EnergyNode solves this problem by running within your company where it can speak directly to your server(s). It acts as a broker between your systems and EnergyLink using an outgoing connection, similar to how your phone handles email and notifications. This enables two-way communication with minimal maintenance.