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, Alteryx API can be called from .Net code using the information here. Several basic examples are available on the Alteryx forums.
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.
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.
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.
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.
}
All APIs return JSON with the following fields. RdWebApi methods return an UploadResult object with them.
Element Name | Element Type | Example | Note |
---|---|---|---|
StatusCode | integer | 200 | |
Message | string | The account provided does not have operated upload access. | |
ProcessId | long | 123 | |
BatchId | long? | 456 |
var result = await client.UploadJibXmlAsync(
stream, //e.g. MemoryStream, FileStream, OracleBlob
"file.xml",
new DateTime(2018, 11, 30, 12, 0, 0, DateTimeKind.Local)
);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
FileName | file.zip | |
AccountingMonth | 2018-11-30 |
Request body is the file data.
var result = await client.UploadJibCsvAsync(
stream, //e.g. MemoryStream, FileStream, OracleBlob
"file.xml",
new DateTime(2018, 11, 30, 12, 0, 0, DateTimeKind.Local)
);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
FileName | file.zip | |
AccountingMonth | 2018-11-30 |
Request body is the file data.
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
);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
FileName | file.zip | |
AccountingMonth | 2018-11-30 | |
OpOrgNum | ORG1 | |
PaymentType | ACH |
Request body is the file data.
var result = await client.UploadVoidCheckAsync(
stream, //e.g. MemoryStream, FileStream, OracleBlob
"file.xml"
);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
FileName | file.zip |
Request body is the file data.
var result = await client.UploadDeskAuditXmlAsync(
stream, //e.g. MemoryStream, FileStream, OracleBlob
"file.xml"
);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
FileName | file.zip |
Request body is the file data.
var result = await client.UploadArBalancesAsync(
stream, //e.g. MemoryStream, FileStream, OracleBlob
"file.xml"
);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
FileName | file.zip |
Request body is the file data.
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.
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!
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
Accept | text/xml |
Request JSON Name | Sample Value | Note |
---|---|---|
From |
"2024-08-23" | |
To |
"2024-11-21" | |
InvoiceTypes optional |
["JIB"] | |
ArRiskCode optional |
["WriteOff","Offset"] | |
OperatorDisputeStatuses optional |
["Interdept","PrtAccept"] | |
MyBa optional |
"My Company" | |
OrgNum optional |
"1111" | |
InvoiceNumber optional |
"12345" | |
InvoiceNumberExactSearch optional |
true | |
ShowToleranceMonths optional |
3.0m | |
ArAccount optional |
"100.10" | |
PartnerName optional |
"Partner Name" |
See the ArManagementData XML schema for details on the response body for this call. The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.
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 Automation 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.
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!
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
Accept | text/xml | |
InvoiceType optional |
JIB |
The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.
{
"Vouchers": {
"XmlSchemaVersion": "2018-11-22",
"Header": {
"RequestLoginName": "User1",
"RequestUserId": 1234,
"RequestBaId": 5678,
"RequestDate": "2019-01-01T01:02:03.0000000-06:00",
"RequestMethod": "VouchersForDownloadXml"
},
"Vouchers": [
{
"VoucherId": 1122334,
"VoucherReference": "VCH_REFER",
"VoucherAccountingMonth": "2016-01-01T00:00:00",
"DocType": "JIB",
"VoucherAmount": 1234.56,
"FileName": "1122334_VCH_REFER_5555_201601.xml",
"FileSize": 123456,
"ActionTypeDescription": "Voucher",
"FirstProcessedUserId": 654321,
"FirstProcessedUserName": "User2",
"FirstProcessedDate": "2015-12-31T00:00:00.000000",
"FirstDownloadedUserId": 654321,
"FirstDownloadedUserName": "User2",
"FirstDownloadedDate": "2015-12-31T00:05:00.000000",
"LastProcessedUserId": 654321,
"LastProcessedUserName": "User2",
"LastProcessedDate": "2019-01-01T00:00:00.000000",
"LastDownloadedUserId": 555555,
"LastDownloadedUserName": "User3",
"LastDownloadedDate": "2019-02-01T00:00:00.000000",
"VoucherInvoices": [
{
"InvoiceId": 22222,
"InvoiceNumber": "0123456789012345",
"PlVersion": 0,
"VoucherInvoiceAmount": 1234.56
}
]
}
]
}
}
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!
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
Accept | text/xml |
The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.
{
"Vouchers": {
"XmlSchemaVersion": "2018-11-22",
"Header": {
"RequestLoginName": "User1",
"RequestUserId": 1234,
"RequestBaId": 5678,
"RequestDate": "2019-01-01T01:02:03.0000000-06:00",
"RequestMethod": "VouchersForDownloadXml"
},
"Vouchers": [
{
"VoucherId": 1122334,
"VoucherReference": "VCH_REFER",
"VoucherAccountingMonth": "2016-01-01T00:00:00",
"DocType": "Revenue",
"VoucherAmount": 1234.56,
"FileName": "1122334_VCH_REFER_5555_201601.xml",
"FileSize": 123456,
"ActionTypeDescription": "Voucher",
"FirstProcessedUserId": 654321,
"FirstProcessedUserName": "User2",
"FirstProcessedDate": "2015-12-31T00:00:00.000000",
"FirstDownloadedUserId": 654321,
"FirstDownloadedUserName": "User2",
"FirstDownloadedDate": "2015-12-31T00:05:00.000000",
"LastProcessedUserId": 654321,
"LastProcessedUserName": "User2",
"LastProcessedDate": "2019-01-01T00:00:00.000000",
"LastDownloadedUserId": 555555,
"LastDownloadedUserName": "User3",
"LastDownloadedDate": "2019-02-01T00:00:00.000000",
"VoucherInvoices": [
{
"InvoiceId": 22222,
"InvoiceNumber": "0123456789012345",
"VoucherInvoiceAmount": 1234.56
}
]
}
]
}
}
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!
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
Returns the data file for the voucher using Content-Type application/octet-stream. Actual format of the file depends which voucher type the client is using (e.g. CSV, TXT, XLSX). For filename and size, see the Content-Disposition header.
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!
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
Returns the PDF files for the voucher using Content-Type multipart (where each part is one PDF using Content-Type application/octet-stream). For filename and size, see the Content-Disposition header of each part.
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.
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. 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.
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(2024, 11, 18, 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!
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
Accept | text/xml |
Request JSON Name | Sample Value | Note |
---|---|---|
DateSearchType |
"LAST_PROCESSED_DATE" | |
From |
"2024-11-18T00:00:00.0000000-07:00" | |
To optional |
"2024-11-21T00:00:00.0000000-07:00" | |
InvoiceType optional |
"JIB" | |
Status optional |
["AcceptedModified","AcceptedAsIs"] |
See the NonOpInvoices XML schema for details on the response body for this call. The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.
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(2024, 11, 18, 12, 0, 0, DateTimeKind.Local)
);
}
All values from the NonOpRevenue XML schema are automatically deserialized into .Net classes within the NonOpRevenueElement class. Happy coding!
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
Accept | text/xml |
Request JSON Name | Sample Value | Note |
---|---|---|
DateSearchType |
"LAST_PROCESSED_DATE" | |
From |
"2024-11-18T00:00:00.0000000-07:00" | |
To optional |
"2024-11-21T00:00:00.0000000-07:00" | |
Status optional |
["AcceptedModified","AcceptedAsIs"] |
See the NonOpRevenue XML schema for details on the response body for this call. The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.
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(2024, 11, 18, 12, 0, 0, DateTimeKind.Local)
);
}
All values from the NonOpRoyalties XML schema are automatically deserialized into .Net classes within the NonOpRoyaltiesElement class. Happy coding!
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
Accept | text/xml |
Request JSON Name | Sample Value | Note |
---|---|---|
DateSearchType |
"LAST_PROCESSED_DATE" | |
From |
"2024-11-18T00:00:00.0000000-07:00" | |
To optional |
"2024-11-21T00:00:00.0000000-07:00" | |
Status optional |
["AcceptedModified","AcceptedAsIs"] |
See the NonOpRoyalties XML schema for details on the response body for this call. The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.
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!
Parameter Name | Description |
---|---|
id:long | The invoice ID that comes from the search result InvoiceSearch |
summarization:alpha |
(Optional) Can be one of the following values:
|
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
Accept | text/xml |
See the NonOpInvoices XML schema for details on the response body for this call. The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.
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!
Parameter Name | Description |
---|---|
id:long | The invoice ID that comes from the search result RevenueSearch |
summarization:alpha |
(Optional) Can be one of the following values:
|
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
Accept | text/xml |
See the NonOpRevenue XML schema for details on the response body for this call. The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.
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!
Parameter Name | Description |
---|---|
id:long | The invoice ID that comes from the search result RoyaltySearch |
summarization:alpha |
(Optional) Can be one of the following values:
|
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
Accept | text/xml |
See the NonOpRoyalties XML schema for details on the response body for this call. The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.
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.
}
GET https://api.energylink.com/Api/NonOpAutomation/ValidateInvoice/{id:long}
Identifies all issues preventing this invoice from being vouchered (e.g. missing approval).
var result = await client.ValidateInvoiceAsync(123);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
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);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
[{
"AfeId": 123,
"PrtAfeNumber": "AFE123", // Optional
"PrtCostCenterCode": "CC123", // Optional
"PrtAfeActionCode": "DisputeSold" // Optional
}]
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);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
[{
"CcId": 123,
"PrtCostCenterCode": "CC001", // Optional
"PrtAfeNumber": "AFE001", // Optional
"PrtCostCenterActionCode": "DisputeSold" // Optional
}]
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);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
[{
"AfeId": 123,
"AfeXrefId": 456,
"PrtAfeNumber": "AFE123", // Optional
"PrtCostCenterCode": "CC123", // Optional
"PrtAfeActionCode": "DisputeSold" // Optional
}]
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);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
[{
"CcId": 123,
"CcXrefId": 456,
"PrtCostCenterCode": "CC001", // Optional
"PrtAfeNumber": "AFE001", // Optional
"PrtCostCenterActionCode": "DisputeSold" // Optional
}]
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);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
{
"DisputeReason": "Sold",
"DisputeComment": "Our records indicate that we have sold this property",
"DisputeEntireStatement": true
}
// To undo a specific detail dispute, enter the full billed amount into AcceptedPartnerAmount
{
"DisputeReason": "Self_Insured",
"DisputeComment": "Our records indicate that this property is self-insured",
"DetailDisputes": [
{
"DetailId": 123,
"AcceptedPartnerAmount": null
}
]
}
var invoiceIds = new List<long> { 12345, 12346 };
var result = await client.ApproveInvoicesAsync(invoiceIds);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
[
12345,
12346
]
//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);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
{
"ExceptDetailIds": [
12345,
12346
]
}
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);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
[
{
"InvoiceId": 123,
"VoucherReference": "VCH123",
"VoucherAccountingMonth": "2011-07-14T12:00:00-0600"
},
{
"InvoiceId": 234,
"VoucherReference": "VCH234",
"VoucherAccountingMonth": "2011-07-14T12:00:00-0600"
}
]
[
{
"InvoiceId": 123,
"VoucherId": 986
},
{
"InvoiceId": 234,
"VoucherId": 987
}
]
Identifies all issues preventing this voucher from being created (e.g. missing approval).
var result = await client.ValidateVoucherAsync(123);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
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);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
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}");
}
}
}
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 as response as needed. This will have to be set up with EnergyLink support
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.
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;
}
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
The response will be in JSON. There are possibly more fields depending on your company's configuration. Your configuration can be updated by EnergyLink Support. The VoidCheckFile is a binary file that has been encoded to a base 64 string. Decode it from a base 64 string into an array of bytes and that can be saved as a file.
{
"OpInquiryPlusElectronicPaymentJson": {
"Inquiries": [
{
"InquiryId": 10000,
"OwnerId": "99999",
"OwnerName": "TEST OIL AND GAS",
"RequestType": "New",
"RelationshipToOwner": "Self",
"TaxId": "1234",
"AccountType": "Checking",
"ABARouting": "999999999",
"BankName": "BANK OF BANK",
"BankAddress": "123 FAKE STREET",
"BankCity": "BANK CITY",
"BankState": "TX",
"BankZip": "00000-0000",
"NameOnBankAccount": "Test Person",
"AccountNumber": "12345678",
"EmailForPaymentNotification": "my.email@email.com",
"VoidCheck": {
"VerifyDocFileName": "10000 EnergyLink Jan 2023 Invoice MTD.123456.DEMO Summary.OPERATOR CORP.pdf",
"VoidCheckFile": "base_64_pdf"
},
"Signature": "Test Person",
"CreatedTS": "2023-04-01T14:31:02.969553-06:00",
"CreatedUser": "Test Person"
}
]
}
}
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;
}
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
The response will be in JSON'
{
"OpInquiryPlusChangeOfAddressJson": {
"Inquiries": [
{
"InquiryId": 82297,
"OwnerId": "9999999",
"OwnerName": "FAKE OWNER LLC",
"TaxId": "4632",
"Address1": "123 FAKE STREET",
"Address2": "",
"Address3": "",
"City": "FAKE CITY",
"State": "TX",
"Country": "USA",
"Zip": "00000",
"Phone": "9999999999",
"Email": "test.person@email.com",
"Signature": "Test Person",
"CreatedTS": "2023-04-01T17:04:15.349934-06:00",
"CreatedUser": "Test Person"
}
]
}
}
var result = await client.UploadInquiryOwnerInfoCsvAsync(
stream, //e.g. MemoryStream, FileStream, OracleBlob
"file.csv.zip"
);
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== | |
FileName | file.zip |
Request body is the file data.
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
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
[
{
"InquiryId": 123,
"CloseInquiryMessage": "Some message",
},
{
"InquiryId": 234,
"CloseInquiryMessage": "Some message2",
}
]
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
Request Header Name | Sample Value | Note |
---|---|---|
Authorization | Basic VVNFUklEOlBBU1NXT1JE== |
[
{
"InquiryId": 123,
"CloseInquiryMessage": "Some message",
},
{
"InquiryId": 234,
"CloseInquiryMessage": "Some message2",
}
]