ReportingCloud

Errors and Status Codes

Topic

When the Web API can't do what a request asks, it answers with an HTTP status code instead of a result, in most cases with a short message that names the cause. The status, the endpoint and that message tell your application whether a template is missing, a quota is used up, too many requests run at the same time or the Authorization header is missing. The causes below come from the endpoint reference, and the answers to requests without valid credentials, to wrong URLs, methods and headers and to too many requests at the same time were observed on the live service in October 2026.

Status Codes at a Glance

StatusMeaningBody
200 OKThe request succeededThe result, as the endpoint reference describes it
201 Createdtemplates/upload stored the template, or PUT /v1/account/apikey created an API keyEmpty after an upload, the new API key as a JSON string after PUT /v1/account/apikey
204 No Contenttemplates/delete deleted the templateEmpty
400 Bad RequestThe body has no data or no valid document, the named template is not in the template storage, or a query parameter such as returnFormat has a value the endpoint doesn't acceptPlain text, such as Template doesn't exist in storage; for a parameter given without a valid value, such as max of proofing/suggestions, JSON with a Message member
403 ForbiddenThe request is not authorized, a quota of the account is exceeded, the plan doesn't include a requested setting such as PDF/A, more than two merge requests of the account run at the same time, or the web server refuses too many requests from one IP address at the same timePlain text, such as Invalid username/password combination or invalid APIKey; an HTML page when the web server refuses the request
404 Not FoundA URL that matches no endpoint, or a request without a required query parameter, such as language of proofing/check or templateName of templates/pagecountFor a URL below /v1/, JSON: {"Message":"No HTTP resource was found that matches the request URI '…'."}; an HTML page outside /v1/
405 Method Not AllowedA request with another HTTP method than the endpoint's, such as GET for processing/review/trackedchanges, which takes POSTJSON with a Message member, with an Allow header that names the endpoint's method
411 Length RequiredA POST or PUT request that has neither a body nor a Content-Length headerAn HTML page
415 Unsupported Media TypeA POST request with a body but no Content-Type header, or with a Content-Type such as text/plain that the endpoint doesn't readJSON with a Message member
500 Internal Server ErrorA request without an Authorization header, with an empty one or with one that is not the authorization method, a space and the credentials. Some endpoints also answer 500 to a body they can't read, such as a document that is not valid Base64JSON with a Message member, such as {"Message":"An error has occurred."} for missing credentials

Only an answer with a success status holds a result, so check the status before you decode the body.

Bad Request

The endpoint reference lists these causes of 400 Bad Request:

CauseEndpoints
The template is not in the template storagetemplates/delete, templates/download, templates/info, templates/pagecount, templates/thumbnails, document/share
No data in the body, no template uploaded with the request, or the template is not in the template storagedocument/merge, document/findandreplace
A find and replace pair with only one string, an empty find string or a null replacementdocument/findandreplace
No data in the body, no documents, or a document that is missing or not valid Base64document/append
No data in the body, or a body that is not a JSON stringdocument/convert, processing/review/trackedchanges
A returnFormat that is not one of the possible valuesdocument/append, document/convert, document/findandreplace, document/merge
The document is not valid or not in the bodydocument/thumbnails
The extension of the template name is not .doc, .docx, .rtf or .tx, or the template has a wrong format, is not in the format of its extension or is corruptedtemplates/upload

An empty documents array is not an error for POST /v1/document/append: it answers 200 with an empty string instead of a document. The endpoints with more query parameters, the two thumbnails endpoints, GET /v1/proofing/check, GET /v1/proofing/suggestions and POST /v1/processing/review/removetrackedchange, also answer 400 for a parameter value they can't use, and their pages name the cases. For a name that is not stored, GET /v1/templates/exists doesn't answer 400; it returns false.

When you get a 400, compare the template name with the names that GET /v1/templates/list returns, and check that documents and templates travel as a Base64 string in a JSON string, as Managing Templates shows.

Forbidden

403 Forbidden has four kinds of causes: the credentials, the quotas of your plan, a setting your plan doesn't include and the number of requests that run at the same time.

Not Authorized

Every endpoint answers 403 when the request is not authorized, for example because the API key was deleted or the user name or password of a Basic header is wrong. The body of that answer is plain text (text/plain), not JSON. A request with an unknown API key gets this answer:

HTTP/2 403
content-type: text/plain; charset=utf-8

Invalid username/password combination or invalid APIKey

Authentication shows the two forms of the Authorization header.

A Quota Is Exceeded

Three quotas limit what an account does per month or keeps in its storage. When one is used up, the endpoints that count against it answer 403:

QuotaEndpoints
Document quotadocument/append, document/convert, document/findandreplace, document/merge, document/thumbnails, templates/thumbnails, processing/review/removetrackedchange
Template storage quotatemplates/upload
Spell checking quotaproofing/check, proofing/suggestions

What Counts Against Which Quota explains how each request counts.

For a template name that has more than 207 characters or contains # or /, POST /v1/templates/upload answers 403 with the text Template quota exceeded as well, even when the quota isn't used up.

The size of each quota depends on your plan, as Plans and Licensing lists. To see how much is left, call GET /v1/account/settings: it returns createdDocuments with maxDocuments, uploadedTemplates with maxTemplates and proofingTransactions with maxProofingTransactions. The portal shows the same numbers, as Transactions and Statistics describes.

A Setting Your Plan Doesn't Include

A request for returnFormat=PDFA to POST /v1/document/merge, POST /v1/document/append, POST /v1/document/convert or POST /v1/document/findandreplace gets 403 with the plain text Setting is not supported in this license. An Extreme license is required when the account has no Extreme license, as on a trial account. Plans and Licensing shows which plan includes PDF/A.

Too Many Concurrent Requests

POST /v1/document/merge answers 403 when more than two requests of the same account arrive at the same time. Send merge requests one after another or at most two at a time, for example from a queue, and send a refused request again once one of the running requests has finished. Merge Requests Return 403 While Quota Is Left shows a .NET application that never runs more than two.

Apart from that, the web server limits, on every endpoint, the number of requests from one IP address that run at the same time. A request over the limit gets 403 with an HTML page (text/html) of the web server instead of the plain-text message, such as the page HTTP Error 403.501 - Forbidden or the shorter 403 - Forbidden: Access is denied., and the SDKs pass that page on as the message of their exception. The same request, sent again a few seconds later, succeeds. When a 403 comes with an HTML body, wait a few seconds before you send the request again, and send fewer requests at the same time from that address.

Wrong Address, Method, Length or Content Type

The service checks the URL, the HTTP method and the Content-Length and Content-Type headers before the credentials, so these answers come even for a request without valid credentials:

  • 404 Not Found for a URL that matches no endpoint or leaves out a required query parameter.
  • 405 Method Not Allowed for another HTTP method than the endpoint's, HEAD and OPTIONS included. The Allow header of the answer names the endpoint's method, and the JSON body reads, for a GET, {"Message":"The requested resource does not support http method 'GET'."}.
  • 411 Length Required for a POST or PUT that has neither a body nor a Content-Length header. The body is an HTML page that reads HTTP Error 411. The request must be chunked or have a content length. Send Content-Length: 0 with a request that has no body, such as PUT /v1/account/apikey.
  • 415 Unsupported Media Type for a body without a Content-Type header, or with a Content-Type such as text/plain that the endpoint doesn't read. Send Content-Type: application/json with every body.

Requests Without Credentials

The reference documents 403 for a request that is not authorized. A request that has no Authorization header at all gets another answer, for example for GET /v1/fonts/list:

HTTP/2 500
content-type: application/json; charset=utf-8

{"Message":"An error has occurred."}

The same answer comes for an empty Authorization header and for one that is not the authorization method, a space and the credentials, such as ReportingCloud-APIKey: followed by the key. A 500 doesn't always mean missing credentials, though: some endpoints answer it as well to a body they can't read, such as a document that is not valid Base64.

When you get this answer, check first that your code sends the Authorization header with every request, written as the authorization method, a space and the credentials, and then that the body is what the page of the endpoint describes. If both are right, look at the status page, which shows whether the Web API, the portal and the backend are available and lists reported incidents, as Check the Service Status describes.

Processing Time Limit

Six endpoints limit the pure processing time of a request to 60 seconds and cancel the task after that: POST /v1/document/append, POST /v1/document/convert, POST /v1/document/findandreplace, POST /v1/document/merge, POST /v1/document/thumbnails and GET /v1/templates/thumbnails. A typical document takes less than 5 seconds. When a request takes longer, make it smaller: fewer data rows and a smaller template for a merge, fewer or smaller documents for an append, a smaller document or template for a conversion or a find and replace, and fewer pages for thumbnails. The reference doesn't name the status code of a canceled request.

The PHP SDK waits up to 120 seconds for an answer by default, longer than the limit. Its timeout option changes the value.

Test Mode

While you develop, the optional query parameter test=true of POST /v1/document/merge, POST /v1/document/convert, POST /v1/document/findandreplace and POST /v1/document/append creates documents with a "TEST MODE" watermark that don't count against the document quota. On a trial account, this holds for document/convert and document/append only: a test run of document/merge or document/findandreplace counts against the document quota like any other request. The following command merges the demo template with the merge data in merge.json, as the Overview of the Mail Merge section builds it:

curl -X POST "https://api.reporting.cloud/v1/document/merge?templateName=sample_invoice.tx&returnFormat=PDF&test=true" \
  -H "Authorization: ReportingCloud-APIKey $REPORTINGCLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @merge.json

Test requests are handled with a lower priority. Test mode is not available with a Free account, as Test Mode in Plans and Licensing describes. In the .NET SDK, MergeDocument, ConvertDocument, FindAndReplaceDocument and AppendDocument have a test parameter. In the PHP SDK, the option 'test' => true of the ReportingCloud constructor adds test=true to every request the instance sends.

Handle Errors in the SDKs

The SDKs turn a refused request into an exception.

.NET. Every method of the NuGet package TXTextControl.ReportingCloud throws a System.ArgumentException when the status is not a success code. The exception's Message is the body of the answer as it came, such as {"Message":"An error has occurred."}, and the exception has no property for the status code. When the service can't be reached, the methods throw an AggregateException whose inner exception is an HttpRequestException.

PHP. The SDK textcontrol/textcontrol-reportingcloud throws two exception classes of the namespace TextControl\ReportingCloud\Exception, which both implement its ExceptionInterface:

ExceptionThrown whengetCode()
RuntimeExceptionThe Web API answered with a 4xx or 5xx status, or could not be reachedThe HTTP status, or 0 without an answer
InvalidArgumentExceptionThe SDK refused an argument before sending the request, such as a template name with an unsupported extension, a dictionary not on its list or a missing API key0

The message of a RuntimeException names the request and the status, followed on the next line by the body of the answer, cut off after 120 characters, for example:

Client error: `GET https://api.reporting.cloud/v1/templates/pagecount?templateName=invoice.docx` resulted in a `400 Bad Request` response:

The following script reads the page count of a template and handles each case:

<?php

declare(strict_types=1);

require_once __DIR__ . '/vendor/autoload.php';

use TextControl\ReportingCloud\Exception\InvalidArgumentException;
use TextControl\ReportingCloud\Exception\RuntimeException;
use TextControl\ReportingCloud\ReportingCloud;

$reportingCloud = new ReportingCloud([
    'api_key' => (string) getenv('REPORTINGCLOUD_API_KEY'),
]);

try {
    $pageCount = $reportingCloud->getTemplatePageCount('sample_invoice.tx');
    printf("%d page(s)\n", $pageCount);
} catch (InvalidArgumentException $exception) {
    // Refused by the SDK, no request was sent
    echo 'Invalid argument: ', $exception->getMessage(), PHP_EOL;
} catch (RuntimeException $exception) {
    $status = $exception->getCode();

    if (400 === $status) {
        echo 'The template is not in the template storage.', PHP_EOL;
    } elseif (403 === $status) {
        echo 'Not authorized: check the API key.', PHP_EOL;
    } else {
        // For example 0 when the service could not be reached
        echo "Request failed ({$status}): ", $exception->getMessage(), PHP_EOL;
    }
}

The code works as follows:

  • getTemplatePageCount checks the template name before it sends anything. A name with a path or an unsupported extension throws the InvalidArgumentException.
  • Every answer with a 4xx or 5xx status throws the RuntimeException, and getCode() returns the status. The script tells the two causes of this endpoint apart by it.
  • A connection error throws a RuntimeException as well, with the code 0.

Java and Ruby. The Java SDK throws an IllegalArgumentException and the Ruby gem raises a RuntimeError, each with the body of the answer as its message and without the status code. Java and Ruby in the SDKs section describe them.