Rewrite rules for short URL

There are two types of rewrite rules you can generate via the API and add_rewrite_rule: internal rules and external rules. External rules get inserted into your .htaccess file, and must be in a format that can be parsed by Apache’s mod_rewrite. Internal rules get added to the WordPress internal rewrite rule array and must be in a format that can be parsed by the API via PHP.

WordPress determines what type of rule you’re adding by looking at the second argument of add_rewrite_rule. Internal rules must point to index.php, with query vars appended via query string. Any rule that doesn’t point to index.php is assumed to be an external rule.

The issue you have with your rules is that you’ve got a mixture of internal and external formats. I assume the first rule works because the trailing slash confuses the parser and it gets added as an external rule. The second rule doesn’t work because it’s interpreted as an internal rule, but uses $1 instead of $matches[1] to access the captured matches.

With these things in mind, this rule should work as an internal rule:

add_action('init', 'pins_add_rewrite_rule', 11);
function pins_add_rewrite_rule() {
    add_rewrite_rule(
        '^document/(.*)$',
        'index.php?action=short_url&id=$matches[1]',
        'top'
    );
}

As for intercepting these requests- you can hook a much earlier action that runs before the database is queried so these requests will be fulfilled much more quickly with less overhead.

add_action( 'parse_query', 'pins_short_url_redirect' );
function pins_short_url_redirect( $wp_query ) {
    if( isset( $wp_query->query_vars['action'] ) && 'short_url' == $wp_query->query_vars['action'] ){
        // output file here
        die();
    }
}

A few other bits:

Use the query_vars filter to add query vars instead of accessing the $wp object directly within init.

action is already a WordPress query var, I don’t know if re-registering it will have any negative impact, it’s best to be safe and use something you know will be unique, like ghazanfar_action. You should probably do the same for id.

Don’t flush rewrite rules on every request. Rewrites should only be flushed when you know they’ve changed. You can flush on plugin activation, or quickly flush for testing purposes by visiting the Permalinks Settings page in admin. See flush_rewrite_rules for more info.