# Errors and Status Codes

> Trace a refused request to its cause: 400 for missing templates, 403 for invalid keys, quotas or too many requests, 405, 411, 415, 500 and SDK exceptions.

- **Product:** ReportingCloud
- **Section:** Documentation
- **Kind:** Topic
- **Path:** ReportingCloud > Document Processing > Errors and Status Codes
- **4521 words**
- **Web URL:** https://docs.textcontrol.com/reporting-cloud/document-processing/errors/
- **LLMs URL:** https://docs.textcontrol.com/reporting-cloud/document-processing/errors/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/reporting-cloud/document-processing/errors/llms-full.txt

---

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

| Status | Meaning | Body |
|---|---|---|
| `200 OK` | The request succeeded | The result, as the endpoint reference describes it |
| `201 Created` | `templates/upload` stored the template, or `PUT /v1/account/apikey` created an API key | Empty after an upload, the new API key as a JSON string after `PUT /v1/account/apikey` |
| `204 No Content` | `templates/delete` deleted the template | Empty |
| `400 Bad Request` | The 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 accept | Plain 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 Forbidden` | The 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 time | Plain text, such as `Invalid username/password combination or invalid APIKey`; an HTML page when the web server refuses the request |
| `404 Not Found` | A URL that matches no endpoint, or a request without a required query parameter, such as `language` of `proofing/check` or `templateName` of `templates/pagecount` | For a URL below `/v1/`, JSON: `{"Message":"No HTTP resource was found that matches the request URI '…'."}`; an HTML page outside `/v1/` |
| `405 Method Not Allowed` | A request with another HTTP method than the endpoint's, such as `GET` for `processing/review/trackedchanges`, which takes `POST` | JSON with a `Message` member, with an `Allow` header that names the endpoint's method |
| `411 Length Required` | A `POST` or `PUT` request that has neither a body nor a `Content-Length` header | An HTML page |
| `415 Unsupported Media Type` | A `POST` request with a body but no `Content-Type` header, or with a `Content-Type` such as `text/plain` that the endpoint doesn't read | JSON with a `Message` member |
| `500 Internal Server Error` | A 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 Base64 | JSON 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`:

| Cause | Endpoints |
|---|---|
| The template is not in the template storage | `templates/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 storage | `document/merge`, `document/findandreplace` |
| A find and replace pair with only one string, an empty find string or a `null` replacement | `document/findandreplace` |
| No data in the body, no `documents`, or a document that is missing or not valid Base64 | `document/append` |
| No data in the body, or a body that is not a JSON string | `document/convert`, `processing/review/trackedchanges` |
| A `returnFormat` that is not one of the possible values | `document/append`, `document/convert`, `document/findandreplace`, `document/merge` |
| The document is not valid or not in the body | `document/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 corrupted | `templates/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](https://docs.textcontrol.com/reporting-cloud/document-processing/templates/#list-and-count-the-stored-templates) returns, and check that documents and templates travel as a Base64 string in a JSON string, as [Managing Templates](https://docs.textcontrol.com/reporting-cloud/document-processing/templates/#upload-a-template) 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:

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

Invalid username/password combination or invalid APIKey
```

[Authentication](https://docs.textcontrol.com/reporting-cloud/document-processing/authentication/llms-full.txt) 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`:

| Quota | Endpoints |
|---|---|
| Document quota | `document/append`, `document/convert`, `document/findandreplace`, `document/merge`, `document/thumbnails`, `templates/thumbnails`, `processing/review/removetrackedchange` |
| Template storage quota | `templates/upload` |
| Spell checking quota | `proofing/check`, `proofing/suggestions` |

[What Counts Against Which Quota](https://docs.textcontrol.com/reporting-cloud/overview/licensing/#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](https://docs.textcontrol.com/reporting-cloud/overview/licensing/llms-full.txt) lists. To see how much is left, call [GET /v1/account/settings](https://docs.textcontrol.com/reporting-cloud/reference/web-api/account/settings/get/llms-full.txt): it returns `createdDocuments` with `maxDocuments`, `uploadedTemplates` with `maxTemplates` and `proofingTransactions` with `maxProofingTransactions`. The portal shows the same numbers, as [Transactions and Statistics](https://docs.textcontrol.com/reporting-cloud/portal/statistics/llms-full.txt) 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](https://docs.textcontrol.com/reporting-cloud/overview/licensing/llms-full.txt) 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](https://docs.textcontrol.com/reporting-cloud/troubleshooting/quotas-limits/#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`:

```text
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](https://docs.textcontrol.com/reporting-cloud/troubleshooting/support/#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](https://docs.textcontrol.com/reporting-cloud/mail-merge/overview/llms-full.txt) of the Mail Merge section builds it:

```bash
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](https://docs.textcontrol.com/reporting-cloud/overview/licensing/#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`:

| Exception | Thrown when | `getCode()` |
|---|---|---|
| `RuntimeException` | The Web API answered with a `4xx` or `5xx` status, or could not be reached | The HTTP status, or `0` without an answer |
| `InvalidArgumentException` | The 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 key | `0` |

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:

```text
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
<?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](https://docs.textcontrol.com/reporting-cloud/sdks/java/#errors) and [Ruby](https://docs.textcontrol.com/reporting-cloud/sdks/ruby/#errors) in the SDKs section describe them.

### Related Pages

- [Authentication](https://docs.textcontrol.com/reporting-cloud/document-processing/authentication/llms-full.txt): send the `Authorization` header with every request.
- [Plans and Licensing](https://docs.textcontrol.com/reporting-cloud/overview/licensing/llms-full.txt): the quotas of each plan and the restrictions of trial and Free accounts.
- [Troubleshooting](https://docs.textcontrol.com/reporting-cloud/troubleshooting/llms-full.txt): problems sorted by what you see, with their fixes.
- [Quotas and Limits](https://docs.textcontrol.com/reporting-cloud/troubleshooting/quotas-limits/llms-full.txt): fix a `403` from a used-up quota or too many requests at the same time.
- [Web API](https://docs.textcontrol.com/reporting-cloud/reference/web-api/llms-full.txt): the endpoints and the errors each one lists.

---

## Nearby

- **Previous:** [Tracked Changes](https://docs.textcontrol.com/reporting-cloud/document-processing/tracked-changes/llms-full.txt)
- **Next:** [SDKs](https://docs.textcontrol.com/reporting-cloud/sdks/llms-full.txt)
