Conversion to typescript from javascript
Use explicit type annotation only when necessary In the vast majority of cases, TypeScript can infer the type of an expression automatically, without you having to note it manually. It's probably better and easier to let TS take care of it - it makes for less code to read and write. If you're ever not sure what sort of type an expression is, use an IDE which can tell you (like VSCode, which works really well with TypeScript - just hover over an expression and it'll tell you what type TypeScript has inferred for it).
For example:
const fileURL: string = URL.createObjectURL(file);
const link: HTMLAnchorElement = document.createElement('a');

can be
const fileURL = URL.createObjectURL(file);
const link = document.createElement('a');

IMO, TypeScript shines most when it looks almost identical to JavaScript, with the added bonus of warning you when you're doing something that's type-unsafe.

I think the only code I can't figure out how to convert over is the blob type.

Its type is a Blob, which you can see in VSCode (or any other good TS-aware IDE):

But, as said, better to let TS infer it automatically; no need for const file: Blob =.
Prefer generics over type assertion Generics are the most elegant way to denote the type of a particular expression, when the type isn't set in stone by the function signature. The as keyword, in contrast, is less type-safe: as tells TypeScript &quot;I know exactly what I'm doing, ignore whatever type this value was determined to be previously and assume that it's of THIS type instead&quot;.
For example, the following code does not throw a TS error, despite the as clearly being nonsensical:
const foo = {
    bar: 'bar'
} as {
    fn?: () =&gt; Promise&lt;Array&lt;string&gt;&gt;
};

as isn't as bad as any, but they both should be avoided when possible.
In your case, getElementById is not generic, unfortunately, but querySelector is. You can change:
const spinner: HTMLElement = document.getElementById('spinner') as HTMLImageElement

to
const spinner = document.querySelector&lt;HTMLElement&gt;('#spinner')!;
// you could also use &lt;HTMLImageElement&gt; if you needed image element specific types

Generics are essentially type arguments that come in &lt;&gt; brackets, before the expression arguments inside the parentheses of the function call. Above, &lt;HTMLImageElement&gt; tells querySelector: &quot;This selector, if it matches, will match an HTMLImageElement.&quot; This is preferable to as because only the types that extend Element can be passed to querySelector.
Function name Functions should describe actions, or what they do. webApiUrl might be more precisely named getApiUrl. (Or, just export the URL string alone: export const apiUrl = '...';, there doesn't seem to be any need for the function)
Error handling If the fetch fails, no indication is given to the user or to other parts of the program. Whenever you have a Promise, you should almost always make sure that possible errors inside it get handled. TSLint rule: no-floating-promises.
While you could add a .catch onto the fetch Promise chain:
    spinner.style.display = 'none'
})
.catch((error) =&gt; {
    // handle errors
});

It might be better to let the caller handle errors as needed. Change:
fetch(`${apiUrl}/api/...

to
return fetch(`${apiUrl}/api/...

so that each caller can do downloadPdf(id, fileName).catch(handleErrors) individually.
Semicolons Some of your lines are missing semicolons. This is stylistically inconsistent and can lead to odd bugs if you forget a semicolon where it's required. Add the missing semicolons, and consider using a linter which warns you when they're missing. (The rationale behind using a linter is similar to the rationale behind using TypeScript - turn annoying, hard-to-encounter, sometimes-hard-to-debug runtime errors into trivially fixable compile-time errors.)