> ## Documentation Index
> Fetch the complete documentation index at: https://docs.markpdf.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Security

> How do you protect your API key and your documents.

# Security

## API key

* Save it in an environment variable or secrets manager, never in the code or in the repository.
* Do not put it in the code, in URLs or in logs (query params are usually registered in proxies).
* Use one key per environment (development, staging, production) and rotate it immediately if it leaks.
* If you integrate the API from an AI agent, do not pass the key to the model as free text: inject it into the code that makes the HTTP call, outside of the prompt.

See [Authentication](/docs/authentication) for the full details of the `x-api-key` header.

## URLs firmadas

`POST /convert/from-url` is intended for **signed and short-lived** URLs of your storage (S3, Supabase, R2, GCS, Azure, or S3-compatible self-hosted):

* They expire quickly (minutes, not days).
* They are not stored by API after processing.
* They are not printed in logs or appear in error messages (including the `413` of raw upload, which never includes the signed URL or internal routes).

<Warning>
  Don't reuse a single long-lived signed URL for multiple documents or share it outside of your backend. If you need to convert the same document again later, generate a new signed URL right before the call.
</Warning>

## Protection SSRF

The URLs you send to `from-url`/`accelerated` are vavalidated before downloading, and the vavalidation is repeated just before connecting (anti DNS-rebinding). The defenses:

| Control                                  | Variable                          | Default              |
| ---------------------------------------- | --------------------------------- | -------------------- |
| Esquemas permitidos                      | `ALLOWED_FETCH_SCHEMES`           | `https`              |
| Allowlist of hosts (recommended in prod) | `ALLOWED_FETCH_HOSTS`             | empty (no allowlist) |
| Puertos permitidos                       | `ALLOWED_FETCH_PORTS`             | `80,443`             |
| Revavalidation before connecting         | `FETCH_REVALIDATE_BEFORE_CONNECT` | `true`               |

Also, always (configurable or not):

* Internal, loopback, link-local and reserved addresses are rejected (`http://localhost`, `169.254.169.254`, private IPs, `[::1]`...), so that API cannot be used as a bridge to internal networks or to the cloud metadata endpoint.
* Any URL with embedded credentials (`user:pass@host`) are rejected.
* Ports outside the allowlist are rejected, so that a malicious URL cannot hit internal services on non-standard ports (Redis 6379, MySQL 3306, administration panels on 8080, etc.).

<Tip>
  The maximum protection is to set `ALLOWED_FETCH_HOSTS` with the hosts of your buckets (e.g. `tu-cuenta.r2.cloudflarestorage.com`). With the allowlist active, only those hosts are accepted and DNS-rebinding is no longer possible, because even the first resolution does not pass if the host is not in the list.
</Tip>

### Why revavalidation matters (DNS rebinding)

An attacker could, in theory, take over a domain that first resolves to a public IP (passes initial vavalidation) and then, between vavalidation and the actual connection, change the DNS to point to an internal IP (`169.254.169.254`, `127.0.0.1`, etc.). With `FETCH_REVALIDATE_BEFORE_CONNECT=true` (default), the host resolves and vavalidates **again just before opening the connection**, closing that time window (TOCTOU — time-of-check to time-of-use).

## Your documents

* The API converts and returns; It does not save your documents persistently.
* The only exception is the result of an automatically queued job (`202`), which is temporarily kept available (\~1 hour) so you can poll, and expires afterward.
* If you need cache, use your own storage and the `ETag` header. See [Cache and ETag](/docs/concepts/caching).
* If you use `output_url` (BYOS) in `/convert/from-url`, the Markdown is uploaded directly to **your** storage, not the API storage.

<Tip>
  For repeated conversions of the same document, take advantage of `ETag` with `If-None-Match` and save bandwidth and conversion cost.
</Tip>

## Security FAQ

* **Can I use HTTP (not HTTPS) for signed URLs?** No by default: `ALLOWED_FETCH_SCHEMES` only allows `https`. It is a deliberate protection against interception in transit.
* **Does API expose any public administration endpoint?** It is not part of the documented contract of this public API; Any management endpoint that exists in your deployment must be protected separately (strong authentication, IP allowlisting).
* **What happens if my storage is not in the `ALLOWED_FETCH_HOSTS` allowlist?** The request is rejected with `403` before attempting the download. Add your bucket host to the corresponding environment variable in your deployment.
* **Do the server logs save the content of my documents?** No; The operation logs record metadata (size, format, times), not the converted content or the complete signed URLs.
