Get started
Up and running with qURL™ in minutes.
- 1
Install the CLI
brew install layervai/tap/qurl qurl version# Download the latest Linux .deb for your architecture: # https://github.com/layervai/qurl-integrations/releases/latest # Save it as qurl.deb, then run these commands in that folder. sudo dpkg -i ./qurl.deb qurl version# Download the latest Linux .rpm for your architecture: # https://github.com/layervai/qurl-integrations/releases/latest # Save it as qurl.rpm, then run these commands in that folder. sudo rpm -U ./qurl.rpm qurl version# Download the archive for your OS and architecture from # https://github.com/layervai/qurl-integrations/releases/latest # darwin | linux | windows × amd64 | arm64 # Extract it and put the qurl binary on your PATH. qurl version # Local publishing works on Windows, macOS, and Linux. - 2
Publish your app
Replace 3000 with your app's port.
qurl publish http://127.0.0.1:3000Published Target: http://127.0.0.1:3000 Status: serving CRID: ae4jqpd7eaoslq7jinmjv4yikgzmcxgpjfsuobiniqnko32lpw743ivbeyhaNo account needed. Save the CRID.
- 3
Share a link
Replace <CRID>. Send the link it prints.
qurl share <CRID> - 4
Stop, start, or delete
qurl list # Pause this resource without deleting its CRID: qurl stop <CRID> # Resume it when needed: qurl start <CRID> # Delete it only when you no longer need it: qurl delete <CRID>
- 1
Download qURL Desktop
Apple Silicon (M1 or later) · macOS 12 Monterey or later.
- 2
Turn on Sharing
Choose Continue without an account to start now, or connect a free LayerV account for higher limits. Then turn on Sharing.
Without an account, you get up to 3 active resources, with links that last up to 24 hours.
- 3
Add a file or a local address
Drag in a file, or enter the address of an app running on your Mac, such as
http://127.0.0.1:3000. - 4
Set expiry and access rules
Choose how long the link lasts, whether it can be reused, and any session, country, or network limits.
- 5
Send the link
Recipients open it in a browser.
Keep your Mac awake, online, and Sharing on.
- 6
Revoke
Revoke a link to stop access to it. Turn Sharing off to stop every share.
- 1
Mint an enrollment token
Choose a connector ID. The example uses
prod-dashboard. Your API key needsqurl:agenthere andqurl:resolvein step 4; the requests call it$LAYERV_TOKEN. Savedata.api_keyfrom the response.# Requires a token with qurl:agent. Save data.api_key from the response; # it is the one-time QURL_API_KEY used for Connector enrollment. curl -X POST https://api.layerv.ai/v1/api-keys \ -H "Authorization: Bearer $LAYERV_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "kind": "enrollment_token", "name": "prod-dashboard Connector enrollment", "target": "connector", "claims": [ { "type": "connector", "id": "prod-dashboard" } ], "expires_in": "1h" }'The token is one-shot and bound to the connector ID. Don't save it in a script.
- 2
Run the connector beside your service
Replace
prod-dashboardwith the connector ID from step 1 and8080with the app's local port. On Docker or Compose, also replacewebwith the app's container or service.# 1 — Write the route config. The local port lives here, not in an env var. cat > qurl-proxy-prod-dashboard.yaml <<'YAML' routes: - type: http local_ip: 127.0.0.1 local_port: 8080 YAML # 2 — Create the state and secret directories. The runtime runs as UID/GID # 65532, so it must own BOTH — it writes its identity into the state dir and # reads the token out of the secret dir. A root-owned 0700 dir the runtime # cannot traverse is the most common reason first start fails. CONNECTOR_ID='prod-dashboard' APP_CONTAINER='web' SECRET_DIR="/run/secrets/qurl-connector/${CONNECTOR_ID}" STATE_DIR="/var/lib/layerv/qurl-connector/${CONNECTOR_ID}/agent" sudo install -d -m 0700 -o 65532 -g 65532 "$SECRET_DIR" sudo install -d -m 0700 -o 65532 -g 65532 "$STATE_DIR" # 3 — Stage the enrollment token in Bash. Run this line on its own. # Do not paste the whole block: queued terminal input can become the token. read -rs -p 'Enrollment token: ' TOKEN < /dev/tty; printf '%s' "$TOKEN" | sudo tee "$SECRET_DIR/api_key" > /dev/null; sudo chown 65532:65532 "$SECRET_DIR/api_key"; sudo chmod 0400 "$SECRET_DIR/api_key"; unset TOKEN # 4 — Run it. Replace vX.Y.Z with the immutable tag from your setup output. docker run -d \ --name "qurl-connector-${CONNECTOR_ID}" \ --network "container:${APP_CONTAINER}" \ --restart=on-failure:5 \ -v "$STATE_DIR:/var/lib/layerv/agent" \ -v "$SECRET_DIR:$SECRET_DIR:ro" \ -v "$PWD/qurl-proxy-${CONNECTOR_ID}.yaml:/work/qurl-proxy.yaml:ro" \ -e QURL_API_KEY_FILE="$SECRET_DIR/api_key" \ -e QURL_CONNECTOR_ID="$CONNECTOR_ID" \ ghcr.io/layervai/qurl-connector:vX.Y.ZRun the
read -rstoken line on its own, not pasted with the rest. Both directories must be owned by65532:65532.# qurl-proxy-prod-dashboard.yaml — mounted read-only below. routes: - type: http local_ip: 127.0.0.1 local_port: 8080 # Add to your Compose project. Replace vX.Y.Z with the immutable tag from # your setup output. Before starting, create the host secret directory with # mode 0700 and owner 65532:65532, then stage api_key with mode 0400 and the same owner. services: qurl-connector-prod-dashboard: image: ghcr.io/layervai/qurl-connector:vX.Y.Z network_mode: "service:web" restart: on-failure environment: QURL_CONNECTOR_ID: "prod-dashboard" QURL_API_KEY_FILE: "/run/secrets/qurl-connector/prod-dashboard/api_key" volumes: - qurl-agent-prod-dashboard:/var/lib/layerv/agent - ./qurl-proxy-prod-dashboard.yaml:/work/qurl-proxy.yaml:ro - /run/secrets/qurl-connector/prod-dashboard:/run/secrets/qurl-connector/prod-dashboard:ro volumes: qurl-agent-prod-dashboard:Run from the Compose directory. Create the secret directory (0700,
65532:65532) andapi_key(0400) first.# qurl-proxy.yaml — ship as a mounted file at /work/qurl-proxy.yaml. routes: - type: http local_ip: 127.0.0.1 local_port: 8080 # Add to the SAME task definition as the app container (awsvpc networking, so # 127.0.0.1 reaches siblings). Mount EFS-backed state — one volume per running # task, never shared. Replace vX.Y.Z with the immutable tag from your setup # output. ECS injects QURL_API_KEY from its native task secrets. { "name": "qurl-connector-prod-dashboard", "image": "ghcr.io/layervai/qurl-connector:vX.Y.Z", "environment": [ { "name": "QURL_CONNECTOR_ID", "value": "prod-dashboard" } ], "secrets": [ { "name": "QURL_API_KEY", "valueFrom": "<secrets-manager-arn>" } ], "mountPoints": [ { "sourceVolume": "qurl-agent-state", "containerPath": "/var/lib/layerv/agent" }, { "sourceVolume": "qurl-route-config", "containerPath": "/work", "readOnly": true } ] }Same task definition as the app. Token in Secrets Manager as
QURL_API_KEY; state on EFS, one volume per task.# ConfigMap key qurl-proxy.yaml — mounted at /work below. routes: - type: http local_ip: 127.0.0.1 local_port: 8080 # Sidecar in the SAME Pod as the app container. One PVC per replica; for # several replicas use a StatefulSet with volumeClaimTemplates. Replace vX.Y.Z # with the immutable tag from your setup output. containers: - name: qurl-connector-prod-dashboard image: ghcr.io/layervai/qurl-connector:vX.Y.Z env: - name: QURL_CONNECTOR_ID value: "prod-dashboard" - name: QURL_API_KEY_FILE value: /run/secrets/qurl-connector/api_key volumeMounts: - name: qurl-agent-state mountPath: /var/lib/layerv/agent - name: qurl-route-config mountPath: /work readOnly: true - name: qurl-enrollment-token mountPath: /run/secrets/qurl-connector readOnly: true volumes: - name: qurl-agent-state persistentVolumeClaim: claimName: qurl-agent-prod-dashboard - name: qurl-route-config configMap: name: qurl-proxy-prod-dashboard - name: qurl-enrollment-token secret: secretName: qurl-enrollment-prod-dashboard items: - key: api_key path: api_keySame Pod as the app. Create the Secret, the ConfigMap, and one PVC per replica.
Replace
vX.Y.Zwith a tag from the connector releases (opens in a new tab). - 3
Verify, then remove the token
Wait for the logs to show a successful connection. Then remove the token.
# Follow the logs until they show a successful connection (Ctrl-C to stop). docker logs -f qurl-connector-prod-dashboard # Remove the enrollment token. sudo rm -f /run/secrets/qurl-connector/prod-dashboard/api_key # Keep the state directory. It holds the connector identity: # /var/lib/layerv/qurl-connector/prod-dashboard/agent# Follow the logs until they show a successful connection (Ctrl-C to stop). docker compose logs -f qurl-connector-prod-dashboard # Remove the enrollment token staged on the host. sudo rm -f /run/secrets/qurl-connector/prod-dashboard/api_key # Keep the named volume qurl-agent-prod-dashboard. It holds the connector identity.# Follow the task's logs until they show a successful connection (Ctrl-C to stop). # <log-group> is the connector container's awslogs-group in the task definition. aws logs tail <log-group> --follow # Remove the QURL_API_KEY entry from the task definition's "secrets" and # deploy it, then delete the enrollment secret. Secrets Manager keeps it in # a recovery window (30 days by default) before it is gone for good. aws secretsmanager delete-secret --secret-id <secrets-manager-arn> # Keep the EFS volume mounted at /var/lib/layerv/agent. It holds the # connector identity.# Follow the logs until they show a successful connection (Ctrl-C to stop). kubectl logs -f <pod-name> -c qurl-connector-prod-dashboard # Remove the qurl-enrollment-token volume and its mount from the Pod spec # and roll it out, then delete the enrollment Secret. kubectl delete secret qurl-enrollment-prod-dashboard # Keep the PVC qurl-agent-prod-dashboard. It holds the connector identity.Keep the connector state. It holds the identity used on every restart, and it is never shared between running connectors.
- 4
Share access
Find the resource's CRID under Protected Resources. Send the link it returns, not the CRID.
# The same API key as the enrollment request; needs qurl:resolve. # <CRID> is on the resource row in your dashboard. curl -X POST https://api.layerv.ai/v1/resources/<CRID>/share \ -H "Authorization: Bearer $LAYERV_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "ttl_seconds": 3600 }' # → { "data": { "qurl": "https://…", "qurl_id": "…", "expires_at": "…" } } # Send the qurl value. It is returned once and never retrievable again.Or, with the CLI:
qurl share <CRID>.