This answer discusses characters may be included inside a URL fragment part without being escaped. I'm posting a separate answer since this part is slightly different than (and can be used in conjunction with) other excellent answers here.
The fragment part is not sent to the server and it is the characters that go after # in this example:
https://example.com/#STUFF-HERE
Specification
The relevant specifications in RFC 3986 are:
fragment = *( pchar / "/" / "?" )
pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
This also references rules in RFC 2234
ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
DIGIT = %x30-39 ; 0-9
Result
So the full list, excluding escapes (pct-encoded) are:
A-Z a-z 0-9 - . _ ~ ! $ & ' ( ) * + , ; = : @ / ?
For your convenience here is a PCRE expression that matches a valid, unescaped fragment:
/^[A-Za-z0-9\-._~!$&'()*+,;=:@\/?]*$/
Encoding
Counting this up, there are:
26 + 26 + 10 + 19 = 81 code points
You could use base 81 to efficiently encode data here.