Shareable document downloads
Redirect a legacy share URL
/a/LinkShareable_get
Redirect a legacy CRM share URL to the isolated download service
When to use this endpoint
Keep existing CRM links working while moving download handling to the session-free public service. Prefer url returned by LinkShareable_new for new integrations. Navigate to this legacy URL rather than expecting FM_api to decode a JSON file response; a valid request returns a redirect and exits.
Authentication and permissions
This compatibility handler has no login requirement and does not query a tenant or switch session context. The dispatcher exempts it from its CRM CSRF check. The supplied pid/id/code are used only to construct the public service URL; possession/token validity is checked at that destination.
A syntactically valid code is not verified by this redirect. The route performs no object/user/page permission or expiry check and does not prove the destination can serve the document. Existing authenticated session state must not be used as a substitute for the URL’s token.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
pid | Positive integer | Yes | Tenant that owns the stored share token. Validated with FILTER_VALIDATE_INT/minimum 1, not permissive intval coercion. It is not installed as the CRM session’s tenant. |
id | Positive integer | Yes | Stored share-token row ID, not the underlying document/file ID. Same positive-integer validation as pid. |
code | 32-character hexadecimal string | Yes | Trimmed and lowercased before validation and redirect; must match exactly 32 hex characters. Missing/blank/nonhex values fail. Uppercase valid hex is accepted after normalization. |
Example request
// Open this URL in a browser, or inspect its redirect without following it.
// The code below is a placeholder, not a working token.
// GET /a/LinkShareable_get?pid=1042&id=1088&code=0123456789abcdef0123456789abcdef
Example response
HTTP/1.1 303 See Other
Location: https://p.fieldmotion.com/linkshareable/?pid=1042&id=1088&code=0123456789abcdef0123456789abcdef
Cache-Control: private, no-store, max-age=0
Referrer-Policy: no-referrer
There is no JSON success body or Content-Disposition/download body at this CRM route. The 303 uses the configured canonical base URL and normalized parameters. The handler does not enforce a request method itself; GET is the browser-link contract, and a valid POST also redirects. Only the separate public service restricts methods to GET/HEAD.
Errors and unusual responses
| Response | Condition |
|---|---|
{"error":"missing or invalid parameter: pid"} | Tenant ID fails positive-integer validation; checked first. |
{"error":"missing or invalid parameter: id"} | Link ID fails positive-integer validation. |
{"error":"missing or invalid parameter: code"} | Normalized code is not exactly 32 hex characters. |
Invalid parameters return legacy CRM JSON errors, which may use HTTP 200. After a valid 303, a followed public-service failure is a separate plain-text HTTP response; do not confuse the two layers.
Behaviour and edge cases
- No database, fileslib, report service, storage read, session mutation or successful-download log occurs in the redirect handler. It does not test whether pid/id/code identifies a real token; a later service 404 is expected for a syntactically valid but unknown combination.
- The canonical download service accepts GET and HEAD, with no CRM session/cookie requirement. It validates positive tenant/link IDs and the 32-hex code against the tenant’s stored token using a constant-time comparison. Anyone holding the complete URL can use it; there is no second recipient or CRM-role check at download time.
- Supported object types are 0=job report, 1=stored file, 2=invoice, 3=quote, 5=purchase order and 6=credit note. Other types or nonpositive stored object IDs yield Download not found. Document rows must be active; quotes/purchase orders additionally require form_id>0. Stored files must be active and type_enum=10 is excluded.
- The service deliberately does not read/enforce edate or one-time metadata. A stored one-year expiry is informational, not a guarantee that the URL expires. Repeated GETs remain possible while the token and accessible object exist; this contract is preserved for legacy links.
- GET generates the current document through the report service, rather than serving a snapshot captured when the token was created. Document responses must contain a valid PDF; non-PDF job output can be converted through the configured document converter. Renderer transport/invalid-response/busy failures are retried up to three attempts with about 1.1 seconds between attempts; permanent renderer errors fail without that retry. Stored files are read through fileslib with the service’s legacy-path fallback.
- HEAD is metadata-only: it validates the object and returns filename/type (plus stored-file size when known) without generating reports, reading file bytes or recording a successful GET audit. HEAD success does not prove subsequent report rendering or archived byte retrieval will succeed.
- Public service errors are plain text with HTTP status: 400 Invalid request for invalid identifiers/code, 404 Download not found for token/object/type absence, 405 Invalid request with Allow: GET, HEAD for other methods, and 503 Download temporarily unavailable for a failed download-resolution operation. These are service responses, not JSON errors from the CRM redirect handler.
- Document filenames are resolved at service access time from tenant job-report-filename, invoice-filename, quote-filename or po-filename settings, with their defaults; credit notes use their sanitized number. Job date formatting uses date-format/timezone with UTC fallback. Path separators/control characters are sanitized, and PDFs receive a .pdf suffix. Stored files use their filename and a limited MIME allowlist, with unknown/unsafe extensions sent as application/octet-stream.
Side effects
- This handler only emits redirect/cache/referrer headers. Following it may generate/convert a report, retrieve or rehydrate stored bytes and attempt a GET access audit. The public service uses attachment responses, no-store/nosniff/no-referrer headers, a sandbox Content-Security-Policy and no session cookie. HEAD avoids body generation and GET audit.
See it in Fieldmotion
CRM1
No direct current CRM1 caller for LinkShareable_get was found. Existing emailed/bookmarked legacy URLs can still reach it. Current signature/support producers build the canonical service URL directly; filter for the legacy GET only when diagnosing an older link.
CRM2
No current CRM2 caller for LinkShareable_get was found. Current app job-report downloads travel through m’s jobReportURL response to the canonical URL. These browser downloads are not authenticated CRM JSON API calls.
Related endpoints
- LinkShareable_new creates a new bearer link and returns the canonical download-service URL.
- LinkShareable_get redirects old CRM URLs; it does not itself download a file or generate a report.
- Link_save saves a Settings bookmark and favicon, a separate workflow with unrelated IDs.