Agent Registration & Queue API Reference
This document provides the technical specification for registering an AI agent (or any host) as a GIIP logical machine, polling its own CQE task queue, and reporting execution results โ the same mechanism giipAgentWin/giipAgentLinux use in production.
๐ Overview
This API lets a host โ a physical/VM machine running giipAgentWin/giipAgentLinux, or an AI coding-agent session with no persistent hardware identity of its own โ register itself in tLSvr as a logical machine (lssn), then poll that lssn's own CQE (Centralized Queue Engine) task queue and report back what happened. All three operations go through one endpoint.
๐ Authentication โ a single dispatcher endpoint, different from the REST-style APIs
This is not the same API shape as the Issue Management API. That API is a normal REST endpoint (
/api/giipIssues) with ax-api-keyheader and real HTTP status codes. This API isgiipApiSk2โ one generic dispatcher endpoint, form-encoded, that always returns HTTP 200.
Base URL
https://giipfaw.azurewebsites.net/api/giipApiSk2
Request shape (always the same three fields)
| Field | Content |
|---|---|
text | The stored-procedure "command" name plus its parameter names, space-separated (e.g. AgentAutoRegister hostname jsondata) โ never parameter values |
token | The SK, as a plain form field. Not an HTTP header. |
jsondata | A JSON object holding the actual parameter values |
curl -s -X POST "https://giipfaw.azurewebsites.net/api/giipApiSk2" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "text=AgentAutoRegister hostname jsondata" \
--data-urlencode "token=${GIIP_API_KEY}" \
--data-urlencode 'jsondata={"hostname":"Lowy-DP01-claude-code","os":"Windows 10"}'
The HTTP status code tells you almost nothing
Every request this endpoint can parse at all returns 200 OK. There is no 401 for an invalid SK, no 404 for a nonexistent lssn, no 403 for a permission problem. The actual outcome is always inside the JSON body, in data[0].RstVal (verified against giipfaw/giipApiSk2/run.ps1 source and live testing, 2026-09-06):
{"data":[{"Proc_MSG":"401|Unauthorized - Invalid SK","RstVal":401,"RstMsg":"Unauthorized - Invalid Secret Key"}]}
The response above is still an HTTP 200. Always check data[0].RstVal, never the HTTP status, to learn whether a giipApiSk2 call actually succeeded.
SK โ same key type as the Issue API, different transport
- Same per-csn project SK issued on
/svclistas the Issue API uses โ but sent astoken=<sk>in the POST body here, not anx-api-keyheader. - The csn is derived server-side from the SK (
dbo.lwGetCSNbysk(@sk)) โ you never send a csn in the request, andAgentAutoRegister/CQEQueueGethave no client-supplied-csn field to silently clamp (verified againstgiipdb/SP/pApiAgentAutoRegisterBySK.sql, 2026-09-06).KVSPuthas a related but different ownership check โ see below.
Endpoints
AgentAutoRegister โ register a logical machine, or heartbeat an existing one
Request:
text=AgentAutoRegister hostname jsondata
token=<sk>
jsondata={
"hostname": "Lowy-DP01-claude-code",
"os": "Windows 10",
"cpu": "...", "cpu_cores": 8, "memory_gb": 32, "disk_gb": 512,
"agent_version": "giip-agent-skill/1.0.0",
"ipv4_global": "...", "ipv4_local": "...",
"network": [{"name":"eth0","ipv4":"...","ipv6":"...","mac":"..."}],
"software": [{"name":"...","version":"...","vendor":"...","type":"..."}],
"services": [{"name":"...","status":"...","start_type":"...","port":80}]
}
Only hostname is required; every other field is best-effort and may be omitted.
Identity key: (hostname, csn). The stored procedure (pApiAgentAutoRegisterBySK) looks up tLSvr by LSHostname = hostname AND CSn = <derived from sk>:
| Existing row? | Action | Response |
|---|---|---|
| No | INSERT a new tLSvr row, lssn = SCOPE_IDENTITY() | {"data":[{"lssn":<new>,"action":"new","RstVal":200,...}]} |
| Yes | UPDATE the existing row (specs, network/software/service inventory, LSLastHeartbeat) | {"data":[{"lssn":<same>,"action":"update","RstVal":200,...}]} |
Live-verified (2026-09-06, csn 47):
$ python scripts/giip_agent.py register --tool-slug claude-code
{"success": true, "hostname": "Lowy-DP01-claude-code", "lssn": 71290, "action": "new"}
$ python scripts/giip_agent.py register --tool-slug claude-code # repeat call
{"success": true, "hostname": "Lowy-DP01-claude-code", "lssn": 71290, "action": "update"}
Mandatory hostname convention: <physical-hostname>-<tool-slug>. Each distinct AI tool on a host must register under its own hostname (and therefore get its own lssn) โ Claude Code, Codex, and Antigravity running on the same PC are three separate logical machines, e.g. Lowy-DP01-claude-code, Lowy-DP01-codex, Lowy-DP01-antigravity. This is a documentation/client convention, not a server-enforced constraint โ the server will happily create whatever hostname you send, so the discipline has to come from the caller.
An invalid SK does not 401 โ it returns RstVal: 401 inside data[0] ({"Proc_MSG":"401|Unauthorized - Invalid SK",...}).
CQEQueueGet โ poll this lssn's queue, once
Request:
text=CQEQueueGet lssn hostname os op
token=<sk>
jsondata={"lssn":71290,"hostname":"Lowy-DP01-claude-code","os":"Windows 10","op":"op"}
Response, work available:
{"data":[{"RstVal":"200","ms_body":"<script or payload text>","mslsn":8032,"script_type":"sh","mssn":123}]}
Response, nothing queued (live-verified, 2026-09-06) โ this is success, not an error:
{"data":[{"RstVal":"404","ProcName":"[pCQEQueueGetbySK02] no queue 2"}]}
$ python scripts/giip_agent.py poll --lssn 71290 --tool-slug claude-code
{"success": true, "lssn": 71290, "has_work": false}
RstVal semantics (verified against giipdb/SP/pApiCQEQueueGetbySK.sql, 2026-09-06):
RstVal | Meaning |
|---|---|
200 | Work is queued; ms_body/mslsn/mssn/script_type are populated |
201 | First-ever poll for a brand-new lssn triggered an implicit re-registration path โ treat like "no work yet," poll again shortly |
0 or 404 | Checked, nothing queued right now โ normal, not an error |
| anything else | A genuine error |
Recommended poll interval: 60 seconds, matching the interval real giipAgentLinux installs use via cron (* * * * *). This API does not rate-limit polling itself, but there is no reason to poll faster โ the queue is populated on GIIP's own schedule.
Wrong lssn looks identical to "no work." If the lssn you poll does not belong to the SK's csn, the join that looks up pending work (tMgmtScriptList ms inner join tLSvr ls on ... where ls.csn = @csn and ms.lssn = @lssn) simply finds no row and returns the same RstVal: 404 as an empty queue. There is no distinct "permission denied" or "lssn not found" response here โ if you suspect you're polling the wrong lssn, re-run register and compare.
KVSPut (kFactor=cqeresult) โ report a result
There is no dedicated "complete"/"ack" verb. Results are written via KVSPut, the same generic key-value mechanism used for all of GIIP's agent telemetry.
Request:
text=KVSPut kType kKey kFactor
token=<sk>
jsondata={
"kType": "lssn", "kKey": "71290", "kFactor": "cqeresult",
"kValue": {"mslsn": 8032, "mssn": 123, "lssn": 71290,
"status": "success", "exit_code": 0,
"stdout": "...", "stderr": ""}
}
kType must be the literal string "lssn"; kKey must be the lssn (as a string) the result belongs to.
Unlike register/poll, this call checks ownership and fails cleanly (verified against giipdb/SP/pApiKVSPutbySk.sql, 2026-09-06): the SP checks EXISTS(SELECT 1 FROM tLSvr WHERE LSsn = @kKey AND CGSn = <derived from sk>) before writing. If the lssn does not belong to the SK's key group:
{"data":[{"RstVal":411,"RstMsg":"..."}]}
Treat any RstVal other than 200 from report as a hard failure โ do not retry it as a heartbeat, and do not silently drop the result.
No unregister / deregister endpoint
There is no delete call for a logical machine. A machine that stops sending heartbeats simply goes stale (LSLastHeartbeat ages) โ there is no API-level "goodbye."
๐ Response Standard
Every giipApiSk2 call wraps its result in a data array, regardless of which of the three commands you called:
{ "data": [ { "RstVal": 200, ... } ] }
- Success:
RstValis200(occasionally201forAgentAutoRegister/CQEQueueGetedge cases).RstValis a number or numeric string depending on the command โ this reference's client script compares it as a string to handle both. - "No work" on poll:
RstVal0or404โ success, not failure. - Failure: any other
RstVal, alongsideRstMsg/Proc_MSG/ProcNamedescribing why. - Transport-level failure (network error, timeout, or an HTTP status other than 200): this means the request never reached the stored procedure at all โ a completely different failure mode from a non-200
RstVal.
๐ค Full procedure for AI agents (register โ poll โ report)
export GIIP_API_KEY="<the SK>"
# 1. Register (idempotent โ the first call creates an lssn, every later call
# with the same hostname is a heartbeat against it)
python scripts/giip_agent.py register --tool-slug claude-code
# {"success": true, "hostname": "Lowy-DP01-claude-code", "lssn": 71290, "action": "new"}
# 2. Poll once. Repeat this on your own schedule (~60s) -- do not loop internally.
python scripts/giip_agent.py poll --lssn 71290 --tool-slug claude-code
# {"success": true, "lssn": 71290, "has_work": false}
# 3. If has_work is true, act on ms_body, then report the outcome.
python scripts/giip_agent.py report --lssn 71290 --mslsn 8032 --mssn 123 \
--status success --exit-code 0
โ ๏ธ Important Notes
- This is a different API family from the Issue Management API. Do not mix the two:
giip-issue's client sends the SK as anx-api-keyheader to REST endpoints with real HTTP status codes;giip-agent's client sends the SK as atokenform field to a single dispatcher endpoint that always returns HTTP 200. - The hostname convention is a client-side discipline, not a server constraint. Nothing stops you from registering
footwice for two different tools โ but doing so creates twolssns that will confuse each other's queue history. Follow<host>-<tool-slug>strictly. - A repeated
registerwith a changed system-info payload is still a heartbeat, not a new machine โ only thehostnamestring determines identity.
Troubleshooting
| Symptom | Cause | Solution |
|---|---|---|
data[0].RstVal is 401 on AgentAutoRegister | Invalid/rotated SK | Re-verify the SK on /svclist |
poll always returns RstVal 404/has_work: false | Either genuinely no queued work, or lssn belongs to a different csn than this SK | Re-run register, compare the returned lssn |
report returns RstVal 411 | lssn does not belong to this SK's key group (cgsn) | Only report against an lssn your own register call returned |
| HTTP status is not 200 at all | Transport-level failure (network, malformed request the router itself rejected) | Distinct from an RstVal failure โ retry is reasonable here after checking connectivity, unlike an RstVal failure |
Two different lssns appear for what you thought was one machine | Hostname was built inconsistently (different --tool-slug, or a bare-hostname override was used) | Always derive hostname as <host>-<tool-slug>, kept stable across calls |
Version: 1.0
Last Updated: 2026-09-06 (live-verified against giipfaw production, csn 47, resulting in lssn 71290)
Source File: giipv3/public/help/giip-agent-api.en.md
v1.0 (2026-09-06, giip #2081): Initial release. Documents
AgentAutoRegister,CQEQueueGet, andKVSPut(kFactor=cqeresult) against thegiipApiSk2dispatcher, based on source review ofgiipdb/SP/pApiAgentAutoRegisterBySK.sql,pApiCQEQueueGetbySK.sql,pApiKVSPutbySk.sql,giipfaw/giipApiSk2/run.ps1, and production client behavior ingiipAgentLinux(scripts/giip-auto-discover.sh,lib/cqe.sh,lib/kvs.sh,cqe/giipCQE.sh). Live-verified: oneregister(new lssn 71290), one repeatregister(heartbeat, same lssn), onepoll(no work queued).
Related Documents: