# Web API

> Request an OAuth access token and convert HTML to PDF with two curl commands, then send the same requests from Node.js with fetch and Base64-encoded JSON.

- **Product:** DS Server
- **Section:** Documentation
- **Kind:** Topic
- **Path:** DS Server > Getting Started > Document Processing > Web API
- **2967 words**
- **Web URL:** https://docs.textcontrol.com/ds-server/getting-started/document-processing/web-api/
- **LLMs URL:** https://docs.textcontrol.com/ds-server/getting-started/document-processing/web-api/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/ds-server/getting-started/document-processing/web-api/llms-full.txt

---

Two HTTP requests are all it takes to have DS Server convert a document: one for an access token, one for the conversion. The steps below send them with curl in a bash shell, convert an HTML document to PDF and save the result, and then repeat the same requests in JavaScript with Node.js. Whatever language your application is written in, the requests stay the same, so the commands also show what your own HTTP client has to send.

### Prerequisites

- **A DS Server.** Either a DS Server of your own that is installed and registered, as the [Installation Guide](https://docs.textcontrol.com/ds-server/installation/llms-full.txt) and [Licensing](https://docs.textcontrol.com/ds-server/administration/licensing/llms-full.txt) describe, or a [trial token](https://docs.textcontrol.com/ds-server/getting-started/trial-token/llms-full.txt) for the trial server `https://trial.dsserver.io`.
- **A security profile that allows the client credentials grant type.** On your own server, create it in the admin portal and turn on **Allow "Client Credentials" grant type**, as [Create a Security Profile](https://docs.textcontrol.com/ds-server/getting-started/document-processing/aspnet-core/#create-a-security-profile) shows. The trial profile allows it already.
- **A bash shell with curl, jq and GNU base64**, as on Linux or in the Windows Subsystem for Linux. jq reads the JSON responses; base64 encodes and decodes the documents.

### Set the Connection Details

Open a terminal and store the address of DS Server and the credentials of the security profile in shell variables, so that the following commands can use them:

```bash
DS_URL="http://localhost:5000"
CLIENT_ID="<client ID>"
CLIENT_SECRET="<client secret>"
```

Replace `http://localhost:5000` with the address of your DS Server, or with `https://trial.dsserver.io` for a trial token, and the placeholders with the client ID and the client secret of the profile. Client IDs that the admin portal creates start with `dsserver.`.

### Request an Access Token

DS Server issues access tokens at the endpoint `/oauth/token`. With the client credentials grant type, you send the client ID and the client secret as an HTTP Basic `Authorization` header, which the `--user` option of curl creates, and the grant type as form data:

```bash
curl --request POST "$DS_URL/oauth/token" \
    --user "$CLIENT_ID:$CLIENT_SECRET" \
    --data-urlencode grant_type=client_credentials
```

DS Server answers with the access token:

```json
{
  "access_token": "<access token>",
  "token_type": "Bearer",
  "expires_in": "3600"
}
```

The token is valid for 3,600 seconds, one hour. Send the request again with jq at the end to store the token in a variable:

```bash
ACCESS_TOKEN=$(curl --silent --request POST "$DS_URL/oauth/token" \
    --user "$CLIENT_ID:$CLIENT_SECRET" \
    --data-urlencode grant_type=client_credentials | jq -r .access_token)
```

The client ID and the client secret must be in the header. Sent only as the form fields `client_id` and `client_secret`, DS Server rejects the request with `Missing authorization header.`

### Convert a Document to PDF

The endpoint `POST /documentprocessing/document/convert` takes a document as a Base64-encoded string in a JSON body and returns the converted document the same way. The format of the result is the query parameter `returnFormat`.

1. Create the document to convert, a small HTML file:

    ```bash
    printf '<h1>Invoice 1001</h1>\n<p>Thank you for your order.</p>\n' > invoice.html
    ```
2. Encode it as Base64 and put it in quotes, which makes it a JSON string, in the file *request.json*:

    ```bash
    printf '"%s"' "$(base64 -w 0 invoice.html)" > request.json
    ```
3. Send it to DS Server with the access token in a `Bearer` header:

    ```bash
    curl --silent --request POST "$DS_URL/documentprocessing/document/convert?returnFormat=PDF" \
        --header "Authorization: Bearer $ACCESS_TOKEN" \
        --header "Content-Type: application/json" \
        --data @request.json \
        --output response.json \
        --write-out '%{http_code}\n'
    ```

    curl saves the response body in *response.json* and prints the status code, `200` when the conversion succeeded.
4. Decode the Base64 string of the response into the PDF file:

    ```bash
    jq -r . response.json | base64 --decode > invoice.pdf
    ```

Open *invoice.pdf* in a PDF viewer: it shows the heading "Invoice 1001" and the line below it.

#### What the Requests Do

- **The token request** posts `grant_type=client_credentials` as `application/x-www-form-urlencoded` data to `/oauth/token`. A JSON body is not accepted there.
- **The conversion request** posts the document as a JSON string, such as `"PGgxPkludm9pY2Ug..."`, and passes the access token as `Authorization: Bearer <access token>`. Instead of the header, you can append the token as the query parameter `access_token`.
- **The response** is a JSON string again: the PDF encoded as Base64. `jq -r .` removes the quotes, and `base64 --decode` turns the string back into binary data.
- **`returnFormat`** accepts `PDF`, `PDFA`, `RTF`, `DOC`, `DOCX`, `HTML`, `TX` and `TXT`; it defaults to `PDF`. The input can be RTF, DOC, DOCX, HTML, PDF or the TX Text Control format; [Supported File Formats](https://docs.textcontrol.com/ds-server/overview/file-formats/llms-full.txt) lists what the Core build on Linux supports.

To convert a Word document, encode it instead of *invoice.html* in step 2. `base64 -w 0` works for binary files as well.

### The Same Requests in JavaScript

Every language with an HTTP client sends the same two requests. The following script does it with the `fetch` function of Node.js 20 or later and needs no package. Save it as *convert.mjs* in the folder that holds *invoice.html*:

```javascript
import { readFile, writeFile } from "node:fs/promises";

const dsUrl = "http://localhost:5000";
const clientId = "<client ID>";
const clientSecret = "<client secret>";

// Request an access token with the client credentials grant type
const tokenResponse = await fetch(`${dsUrl}/oauth/token`, {
  method: "POST",
  headers: {
    "Authorization": "Basic " + Buffer.from(`${clientId}:${clientSecret}`).toString("base64"),
    "Content-Type": "application/x-www-form-urlencoded"
  },
  body: "grant_type=client_credentials"
});
if (!tokenResponse.ok) throw new Error(await tokenResponse.text());
const { access_token } = await tokenResponse.json();

// Send the HTML document as a Base64-encoded JSON string
const html = await readFile("invoice.html");
const convertResponse = await fetch(`${dsUrl}/documentprocessing/document/convert?returnFormat=PDF`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${access_token}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify(html.toString("base64"))
});
if (!convertResponse.ok) throw new Error(await convertResponse.text());

// The response is the PDF document as a Base64-encoded JSON string
const pdf = await convertResponse.json();
await writeFile("invoice.pdf", Buffer.from(pdf, "base64"));
console.log("Saved invoice.pdf");
```

Replace the service URL and the credentials, and run the script:

```bash
node convert.mjs
```

The script prints `Saved invoice.pdf` and writes the same PDF as the curl commands. Run it on a server, not in a browser page: the client secret must not reach the browser.

### Troubleshooting

DS Server answers errors of the token request with status code 400 and a JSON body that names the error:

```json
{
  "error": "unsupported_grant_type",
  "error_description": "This security profile does not allow the specified grant type. (client_credentials)"
}
```

| Response | Cause | Fix |
|---|---|---|
| 400, `unsupported_grant_type` | The security profile doesn't allow the client credentials grant type | Turn on **Allow "Client Credentials" grant type** in the profile |
| 400, `invalid_client`, `Invalid client ID.` or `Invalid client secret.` | The client ID or the client secret is wrong | Copy both again from the profile in the admin portal |
| 400, `invalid_request`, `Missing authorization header.` | The credentials were sent as form fields only | Send them as a Basic header, `--user` in curl |
| 401 with an empty body | OAuth is enabled and the access token is missing, wrong or expired, or the security profile has expired | Request a new token and send it as `Authorization: Bearer <access token>` |
| 400, `DS Server is not registered.` | DS Server has no license yet | Register the server, as [Licensing](https://docs.textcontrol.com/ds-server/administration/licensing/llms-full.txt) describes |
| 400, `One or more validation errors occurred.` | The body is not a JSON string, for example `{}` | Put the Base64 string in quotes, as step 2 does |

When DS Server answers an error, *response.json* holds its message instead of a document, and `base64 --decode` reports invalid input. Look at the file with `cat response.json`.

### Next Steps

- [Authentication](https://docs.textcontrol.com/ds-server/document-processing/authentication/llms-full.txt): access tokens, grant types and how to reuse a token until it expires.
- [Converting Documents](https://docs.textcontrol.com/ds-server/document-processing/converting/llms-full.txt): formats, options and form fields.
- [Merging Templates](https://docs.textcontrol.com/ds-server/document-processing/mail-merge/llms-full.txt): fill a template with JSON data.
- [POST /documentprocessing/document/convert](https://docs.textcontrol.com/ds-server/reference/web-api/documentprocessing/document-convert/post/llms-full.txt) and [POST /oauth/token](https://docs.textcontrol.com/ds-server/reference/web-api/oauth/token/post/llms-full.txt): the parameters and responses of the two endpoints.
- [ASP.NET Core](https://docs.textcontrol.com/ds-server/getting-started/document-processing/aspnet-core/llms-full.txt): the same conversion with the .NET package.

---

## Nearby

- **Previous:** [ASP.NET Core](https://docs.textcontrol.com/ds-server/getting-started/document-processing/aspnet-core/llms-full.txt)
- **Next:** [Document Editor](https://docs.textcontrol.com/ds-server/getting-started/document-editor/llms-full.txt)
