Open durable state first
OpenFileAgentState gives the SDK somewhere to keep the registration. Everything after the first successful enrollment depends on this file surviving restarts — it holds the device credential the platform minted.
Go SDK guide
Give your service a durable device identity so it can take part in private resource access. Enroll once, keep its state across restarts, and use the returned client in your application. Enrollment uses an authenticated outbound channel; publishing a resource and opening an access link are separate steps.
Snippets are written against github.com/layervai/qurl-go@v0.12.0.
Install
Use the Go version required by the SDK’s go.mod or newer. The terminal block initializes a new module; skip that line in an existing project. Set QURL_DEPLOYMENT to the deployment file supplied during LayerV setup. It supplies the Hub trust root used for enrollment. See the versioned SDK guide for deployment configuration and WithAgentRuntimeHub.
# In a new directory; skip init if your project already has go.mod.
go mod init example.com/private-agent
go get github.com/layervai/qurl-go/qurl@v0.12.0# Use the deployment file supplied for your environment.
export QURL_DEPLOYMENT=/path/to/deployment.json
mkdir -p .qurl
chmod 700 .qurlOpenFileAgentState gives the SDK somewhere to keep the registration. Everything after the first successful enrollment depends on this file surviving restarts — it holds the device credential the platform minted.
WithAgentRuntimeEnrollmentCredential takes either an API key or a one-shot enrollment token. It is used only when nothing is registered yet; a start that finds an existing registration ignores it entirely.
Install an OTP provider for the default path, or declare the runtime headless. The two options contradict each other and cannot be combined — one says how to read a code, the other says no code can be read.
The same call enrolls when nothing is registered, resumes an interrupted enrollment, and otherwise opens the existing registration and renews its assignment when needed. A process restarting after a weekend outage runs the same code as one restarting after thirty seconds.
Default path
With deployment configuration in place, give the runtime your API key and a callback that returns the emailed code. The complete file below includes the callback. Set QURL_API_KEY through your secret manager or environment, then run go run . from this directory. Use your own application version in the metadata. Keep .qurl out of source control.
package main
import (
"bufio"
"context"
"fmt"
"io"
"log"
"os"
"os/signal"
"strings"
"github.com/layervai/qurl-go/qurl"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
// Durable state. Keep this file across restarts: it is where the device
// credential lands once enrollment completes.
store, err := qurl.OpenFileAgentState("./.qurl/agent-state.json")
if err != nil {
log.Fatal(err)
}
defer store.Close()
hostname, _ := os.Hostname()
client, binding, err := qurl.ConnectAgentRuntime(ctx, store,
qurl.WithAgentRuntimeEnrollmentCredential(os.Getenv("QURL_API_KEY")),
qurl.WithAgentRuntimeMetadata(hostname, "example"),
qurl.WithAgentRuntimeOTPProvider(readOneTimeCode),
)
if err != nil {
log.Fatal(err)
}
defer binding.Destroy()
_ = client // use it to create portals and share resources
<-ctx.Done() // keep the runtime alive until interrupted
}
// Read the emailed code. For unattended services, replace the
// terminal prompt with your own mailbox or operator workflow.
// Reuse the reader so a retry retains buffered input. This CLI example exits
// if enrollment is cancelled; a pending terminal read ends with the process.
var otpInput = bufio.NewReader(os.Stdin)
func readOneTimeCode(ctx context.Context, challenge qurl.AgentOTPChallenge) (string, error) {
type result struct {
code string
err error
}
input := make(chan result, 1)
fmt.Fprint(os.Stderr, "Email one-time code: ")
go func() {
line, err := otpInput.ReadString('\n')
if err == io.EOF && len(line) > 0 {
err = nil
}
input <- result{strings.TrimSpace(line), err}
}()
select {
case <-ctx.Done():
return "", ctx.Err()
case reply := <-input:
return reply.code, reply.err
}
}The one-time code
The platform emails an 8-digit code to the address on the account, and your callback returns it. This is not a “human” path: agents increasingly have their own mailboxes, and a service account or shared operations alias works just as well. All that matters is that something can read the address the code went to.
At most one code is sent per attempt. The SDK never writes the code to disk. The challenge value passed to your callback is bounded, non-secret context for logging and correlation — it carries neither the credential nor anything replayable.
Return exactly 8 decimal digits, and honor the context: it is already bounded by the enrollment window, so a provider that blocks past it will be canceled rather than left to hang.
Headless path
An unattended build agent or a container in a pipeline — when no address in reach can receive the code, mint a one-shot enrollment token instead and declare the runtime headless. Mount durable storage at /var/lib/layerv/qurl, owned by the service user with mode 0700. The token was minted for exactly one enrollment, so it is its own proof and no code is sent.
Create an enrollment token with target: agent from the console, or from an API key holding qurl:agent. It expires within 24 hours whether it is used or not, so mint it close to the run that will consume it.
Use this complete file instead of the interactive enrollment example.
package main
import (
"context"
"log"
"os"
"os/signal"
"github.com/layervai/qurl-go/qurl"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
// Mount durable, owner-only storage at this path.
store, err := qurl.OpenFileAgentState("/var/lib/layerv/qurl/agent-state.json")
if err != nil {
log.Fatal(err)
}
defer store.Close()
hostname, _ := os.Hostname()
client, binding, err := qurl.ConnectAgentRuntime(ctx, store,
qurl.WithAgentRuntimeEnrollmentCredential(os.Getenv("QURL_ENROLLMENT_TOKEN")),
qurl.WithAgentRuntimeMetadata(hostname, "example"),
qurl.WithAgentRuntimeHeadlessEnrollment(),
)
if err != nil {
log.Fatal(err)
}
defer binding.Destroy()
_ = client // use it to create portals and share resources
<-ctx.Done()
}After enrollment
Enrollment is the only step that needs an API key or an enrollment token. Once it completes, the state file holds the device credential the platform minted, and that is the machine’s identity from then on.
Run the same call on every start. It finds the registration and serves it, renewing the lease and following any relocation on the way.
If your service should not hold an enrollment credential at runtime, drop the credential option entirely: the call can then renew and serve an existing registration but can never create one.
Losing it means the machine has to enroll again with a fresh credential. Keep it on durable storage, back it up as secret-bearing, and do not share one state file between two concurrently running processes.
Device credentials appear in your credential list, so you can see and revoke a machine from the account side.
When enrollment happens somewhere else — an installer, a provisioning job, the first run of an image — leave the credential option off. The same call still renews the lease, follows relocations, and serves the registration; it simply has no way to create one.
This is why ConnectAgentRuntime is the only call your service needs: it does not have to know whether this particular start is the one that enrolls.
// Enrollment happened elsewhere — an installer, a provisioning job, a
// first run that already completed. Without a credential this call can
// renew and serve the existing registration, and can never create one.
client, binding, err := qurl.ConnectAgentRuntime(ctx, store,
qurl.WithAgentRuntimeMetadata(hostname, "example"),
)
if err != nil {
log.Fatal(err)
}
defer binding.Destroy()
_ = client // use it to create portals and share resourcesErrors
The credential will not tell you what it is — you cannot parse a kind out of the token string, and the platform reports it on the first authenticated call. Go by how you got it: issued against an address means the emailed-code path, pre-issued for a machine means headless. Correct the reported credential or enrollment-path error, then retry with the same state file. Preserve that state so interrupted enrollment can resume with the same agent identity.
client, binding, err := qurl.ConnectAgentRuntime(ctx, store,
qurl.WithAgentRuntimeEnrollmentCredential(credential),
qurl.WithAgentRuntimeMetadata(hostname, "example"),
qurl.WithAgentRuntimeOTPProvider(readOneTimeCode),
)
switch {
case errors.Is(err, qurl.ErrAgentOTPRequired):
// The default path needs a way to read the emailed code. Install
// WithAgentRuntimeOTPProvider, or say this runtime has no mailbox with
// WithAgentRuntimeHeadlessEnrollment.
return err
case errors.Is(err, qurl.ErrRegistrationKeyKindDisallowed):
// The credential is a different kind than this enrollment path accepts.
// The error message names the credential the platform actually saw and
// the remedy for it.
return err
case err != nil:
return err
}
// On the success path, use the enrolled runtime.
_ = client
defer binding.Destroy()ErrNoDeploymentHubSet QURL_DEPLOYMENT to the deployment file supplied for your environment. Agent enrollment needs its Hub trust root; WithAgentRuntimeHub can supply it explicitly.
ErrInsecureAgentStatePermissionsMake the state directory accessible only to the runtime owner (mode 0700). Keep the state file private and on durable storage.
ErrAgentOTPRequiredThe default enrollment path was taken with no way to read the emailed code. Returned before any network call, so nothing was sent and nothing enrolled. Install WithAgentRuntimeOTPProvider, or switch to WithAgentRuntimeHeadlessEnrollment with a one-shot enrollment token.
ErrRegistrationKeyKindDisallowedThe credential is real, but it is a different kind than this enrollment path accepts — most often a one-shot enrollment token handed to the default code path, or an API key handed to the headless path. The message names the kind the platform reported and the fix for it.
ErrOTPIncorrect / ErrOTPExpiredThe code your provider returned was wrong or arrived too late. Nothing is registered, and a retry reuses the same agent identity rather than creating a second one.
ErrNoAccountEmailThe account behind the credential has no address the code could be sent to. Add one, or enroll headlessly with an enrollment token.
ErrDeviceKeyQuotaExceededThe account cannot hold another active device credential. Revoke a device credential that no longer has a machine behind it, then retry.
Next
The client returned by enrollment creates portals and shares resources through the same API the reference documents.