Pull HTTP protocol

Use this reference when changing a request or response in the client and server together. If you are adding Reprint to a product, use the client integration guide first; it handles these request details for you. Private cursor and state formats can change between client versions.

Keep the API routing marker (?reprint-api or the legacy ?site-export-api) in the URL. The client keeps every parameter of the supplied API URL, including any custom routing query parameters, and appends endpoint to its query. Those query parameters stay in the URL. Additional export options go in the POST body. For example, POST to /?reprint-api&endpoint=db_index with:

{
  "skip_rows": [
    {
      "table_name_without_prefix": "postmeta",
      "column": "meta_key",
      "value_base64": "X2VkaXRfbG9jaw=="
    }
  ]
}

The exporter still reads export parameters from the query string, where clients sent them before the POST transport. A released token client, recognised by its X-Auth-Content-Hash header, is refused with client_update_required before its request is dispatched. When a parameter appears in both places, the POST value wins for JSON and form bodies. The endpoint is the exception. The server dispatches the query’s value. When the query names none, it takes the body’s value unless that value is a push endpoint. Released key clients send the endpoint in the body, so this fallback keeps them working. Existing multipart file-list uploads keep their file part, and their options may stay in the query string.

For file_fetch, send the requested paths in the multipart file_list upload. Each record’s path uses ordinary base64 or rp.-prefixed ROT13-base64, depending on the source’s preflight capabilities. The server ignores any file_list_path request parameter. That option remains available to direct PHP callers of endpoint_file_fetch().

Requests are signed over newline-joined fields behind a version label. The connection token uses reprint-hmac-sha256-v2, and a key uses reprint-rsa-sha256-v1 plus its key id. The remaining fields are the nonce, the timestamp, the uppercase HTTP method, and the request’s path and query, which name the endpoint. The signature never covers or hashes the body, so a multipart upload streams without buffering.

The exporter accepts application/json, application/x-www-form-urlencoded, and multipart/form-data. The signature covers the URL, endpoint included, and never the body. Use the file push reference for push request fields.

The client sends URL-encoded forms for preflight and streaming pull commands. File-list downloads retain multipart uploads, with options before the file part. Path parameters use the format selected by preflight. Both base64 formats preserve non-UTF-8 filename bytes. Cursors travel only in the body, which also keeps a large cursor clear of host header size limits. The exporter still reads an X-Export-Cursor header when the body has no cursor, for older clients. When changing cursor transport, test continuation with that header removed; a source firewall may discard it.

Multipart array entries use field names such as directory[0] and skip_rows[0][column].

Sending file paths through source firewalls

For an index request selecting /tmp, the client sends rp.Y3EgpN== instead of ordinary base64 (L3RtcA==) when the source reports support. Here are the POST fields after form decoding:

Field Ordinary base64 ROT13-base64 with the rp. prefix
list_dir L3RtcA== rp.Y3EgpN==
directory[0] L3RtcA== rp.Y3EgpN==

The same format applies to pulled_before and other array entries in these directory parameters. The endpoint stays in the URL.

Encoding and decoding a path

The client base64-encodes the path bytes, applies ROT13 to that text, and adds rp.:

$encoded_path = 'rp.' . str_rot13(base64_encode('/tmp'));
// rp.Y3EgpN==

The server recognizes the exact rp. prefix and reverses those steps:

rp.Y3EgpN==  ->  Y3EgpN==  ->  L3RtcA==  ->  /tmp
             strip rp.      ROT13        base64 decode

Decoded directory parameters must be absolute paths in the source host’s path format. Empty values, invalid base64, and relative paths are rejected. A failed rp. value is not tried again as another format. The transform preserves the original bytes, including Windows paths and non-UTF-8 filename bytes.

The period is outside the standard base64 alphabet. For example, base64_decode('rp.Y3EgpN==', true) returns false. The prefix is reserved for this exact format; it selects the Reprint decoder rather than an ordinary base64 decoder.

The prefix adds no percent escapes to a URL-encoded form. For /tmp, the actual form field is:

list_dir=rp.Y3EgpN%3D%3D

Only the payload’s +, /, and = need the same escaping as ordinary base64. Multipart forms carry rp.Y3EgpN== without URL encoding.

File-list uploads

For a download of /tmp/a.txt, the path in the uploaded file_list JSON changes as follows. Other record fields are omitted:

-[{"path":"L3RtcC9hLnR4dA=="}]
+[{"path":"rp.Y3EgpP9uYaE4qN=="}]

This applies when the source reports support, including runtime-file downloads and pending batches prepared by an older client. Immediately before upload, the client writes an encoded temporary copy one entry at a time. Paths already starting with rp. are copied without encoding them again. The input list stays unchanged, and the temporary copy is removed when the request returns or throws. Newly prepared batches count the prefix bytes against the request-body budget. The server accepts both formats, even within one uploaded list.

Cursors sent back to the source

A cursor tells the source where to continue. A file response can return the base64 encoding of this JSON:

{"path":"L3RtcC9hLnR4dA==","bytes":16384}

The client keeps the response cursor unchanged in saved state. When the source reports support, only the copy sent in the next POST changes:

$post_cursor = 'rp.' . str_rot13($response_cursor);

The cursor is already base64, so the client does not base64-encode it again or change its nested paths. The server removes rp., undoes ROT13, then runs the existing base64 and cursor JSON checks. Resuming needs no saved-state conversion.

Choosing a format for older servers

Preflight reports rot13_base64_request_values: true when the source accepts the new format. The client selects the following POST formats:

Source reports directory, list_dir, pulled_before Uploaded file-list paths and POST cursors
rot13_base64_request_values: true rp. + ROT13-base64 rp. + ROT13-base64
Only base64_path_parameters: true Ordinary base64 Ordinary base64
Neither Raw paths Ordinary base64

Preflight itself sends raw paths so it can discover support. New servers still report base64_path_parameters: true and accept ordinary base64 and raw directory paths for older clients. Protocol version 3 remains unchanged.

Response headers, response cursors, saved indexes, and SQL row-filter values keep their existing encodings. Push requests and their multipart part headers are unchanged.

This encoding adds no secrecy or authentication. A permissive base64 decoder can ignore punctuation, and a firewall can learn the transform too. It does not guarantee acceptance by every firewall. HTTPS and signed requests are unchanged.

Output before the response

A blank line before <?php in the site’s index.php can send the HTTP headers before Reprint loads. Preflight still returns its JSON object. Streaming pull endpoints send uncompressed multipart data when they can no longer set headers. The client skips leading spaces, tabs, and line breaks before detecting the --boundary- line in the body. Part headers carry the cursors and other stream metadata as before. The client still reads each part as it arrives.

This fallback does not accept HTML or PHP warning text before the boundary. It also cannot restore an HTTP status or CORS header after headers were sent; a browser may still block a cross-origin request. Remove the early output on the source site to restore those headers and gzip compression.

Test a request-format change

Run the request-body and path-encoding tests from tests/:

../vendor/bin/phpunit Import/MultipartRequestParametersTest.php
../vendor/bin/phpunit Import/RequestUrlPathEncodingTest.php
../vendor/bin/phpunit Import/KeySignedRequestTest.php

When changing the negotiated path format, check both a source that advertises support and one that does not. Include a non-UTF-8 filename and a cursor sent back after interruption; both must arrive at the source with their original bytes.