I'm having an issue when creating a "Purchase Invoice" in the Sage One API. No matter what changes I make to the data being passed in, I seem to get a 500 Internal Service Error, and no detailed response that contains any meaningful information. The response is always the same, no matter what data I enter (with only what seems to be a GUID in the errorCode field and the server time changing). The response is as follows:
{
"$diagnoses": [
{
"$severity": "error",
"$dataCode": "UnexpectedError",
"$message": "Unexpected error",
"$source": {
"serverTime": "2016-04-29T15:48:11.637+00:00",
"errorCode": "3b83e1b5-164d-42d4-95b3-3479b09c93f1",
"requestPath": "/api/v2/purchase_invoices",
"queryString": "",
"requestMethod": "POST"
}
}
]
}
I'm positive this isn't an authorization issue, as I both get and create other data types prior to this. This includes contacts, which are required for creating a Purchase Invoice.
I've followed the information presented at their self-service site. I also started with their bare bones SDK as a basis for most of the underlying process I'm working with. I've since rewritten most the underlying structure thinking originally it might have been caused by the SDK, which led me to the same situation- where I only get 500 Internal Service Errors as a response.
This leads me to believe it's an issue with the parameters themselves, as the documentation has some differences between the parameters listed in the middle column and the data in the example call in the right column. Specifically, the example has extra fields like "extra_reference", and in the line items "product_id" and "product_code" that are not listed in the middle column.
Here is some of the relevant code for the call, again remembering that the basic architecture is theirs, with some procedural modifications to fit into my current architecture that do not affect the actual call:
protected override List<KeyValuePair<string, string>> GetPostDataValuesForCreate(
SageOnePurchaseInvoice objectToCreate)
{
List<KeyValuePair<string, string>> postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("expense[contact_id]", objectToCreate.ContactId.ToString()));
postData.Add(new KeyValuePair<string, string>("expense[date]", objectToCreate.Date));
postData.Add(new KeyValuePair<string, string>("expense[due_date]", objectToCreate.DueDate));
if (!string.IsNullOrWhiteSpace(objectToCreate.Reference))
{
postData.Add(new KeyValuePair<string, string>("expense[reference]", objectToCreate.Reference));
}
if (objectToCreate.ProjectId != 0)
{
postData.Add(new KeyValuePair<string, string>("expense[project_id]", objectToCreate.ProjectId.ToString()));
}
if (!string.IsNullOrWhiteSpace(objectToCreate.Notes))
{
postData.Add(new KeyValuePair<string, string>("expense[notes]", objectToCreate.Notes));
}
for (int i = 0; i < objectToCreate.LineItems.Length; i++)
{
string index = "expense[line_items_attributes][" + i.ToString() + "][";
postData.Add(new KeyValuePair<string, string>(index + "description]", objectToCreate.LineItems[i].Description));
postData.Add(new KeyValuePair<string, string>(index + "ledger_account_id]", objectToCreate.LineItems[i].LedgerAccount.Id.ToString()));
postData.Add(new KeyValuePair<string, string>(index + "quantity]", objectToCreate.LineItems[i].Quantity.ToString()));
postData.Add(new KeyValuePair<string, string>(index + "unit_price]", objectToCreate.LineItems[i].UnitPrice.ToString()));
}
return postData;
}
Here is the actual method that creates and execute the Web Request:
public static string Create(Uri baseUrl, List> bodyParams, string token, string signingSecret) { string result;
string nonce = GenerateNonce();
HttpWebRequest sageOneWebRequest = WebRequest.Create(baseUrl) as HttpWebRequest;
string PostParams = ConvertPostParams(bodyParams);
string signatureString = GetSignatureString(baseUrl, null, bodyParams, nonce, WebRequestMethods.Http.Post);
string signingKey = GetSigningKey(token, signingSecret);
string signature = GenerateHmac(signingKey, signatureString);
sageOneWebRequest.AllowAutoRedirect = true;
sageOneWebRequest.Accept = "*/*";
sageOneWebRequest.UserAgent = "Itemize";
sageOneWebRequest.Headers.Add("X-Signature", signature);
sageOneWebRequest.Headers.Add("X-Nonce", nonce);
sageOneWebRequest.ContentType = "application/x-www-form-urlencoded";
sageOneWebRequest.Timeout = 100000;
sageOneWebRequest.Headers.Add("Authorization", "Bearer " + token);
sageOneWebRequest.Method = WebRequestMethods.Http.Post;
using (StreamWriter requestWriter = new StreamWriter(sageOneWebRequest.GetRequestStream()))
{
try
{
requestWriter.Write(PostParams);
}
catch(Exception ex)
{
throw new Exception("Exception thrown writing Post Params to Body", ex);
}
}
try
{
using (WebResponse response = sageOneWebRequest.GetResponse())
{
Stream dataStream = response.GetResponseStream();
using (StreamReader reader = new StreamReader(dataStream))
{
result = reader.ReadToEnd();
}
}
}
catch (WebException webex)
{
//This is where the error is caught
Logger.Error("Web Exception while processing Sage One Web Request: ", webex);
string text;
using (var sr = new StreamReader(webex.Response.GetResponseStream()))
{
text = sr.ReadToEnd();
}
result = null;
throw new Exception("Web Exception thrown processing Sage One Request", webex);
}
catch (Exception ex)
{
Logger.Error("Exception while processing Sage One Web Request: ", ex);
result = null;
throw new Exception("Exception thrown processing Sage One Request", ex);
}
return result;
}
Any help with this issue would be greatly appreciated! Thanks!
Edit: Regarding the suggestion below, the actual URL path followed is "api.sageone.com/accounts/v2/purchase_invoices", not the requestpath noted in the error received.
You are posting to /purchase_invoices
, yet your parameters are for expense
. Please use purchase_invoice
name to wrap your params.