Why a REST API for the Zebra ZC350?
The Zebra ZC350 is an excellent card printer, but its standard control path isn't. Anyone wanting to integrate the printer into their own workflow runs into three operational frictions: a Windows workstation per printer, a USB cable between workstation and hardware, and a vendor driver stack (CardStudio plus Silex SX Virtual Link) that has to be installed and maintained per workstation. Acceptable for a single reception desk — an operational nightmare for corporate or multi-site setups.
ConoCard offers an alternative at the right level: an HTTP server that drives the printer over IP. One POST call with the desired card data, and the server handles printing, encoding, contactless UID readback and registration in 2N Access Commander, returning JSON. No driver, no USB, no workstation installs.
The primary endpoint: POST /api/issue
One endpoint covers 90% of operational needs. You pass what should be on the card, who the card is for, and optionally which printer (in multi-printer setups). The server takes care of the rest.
Authentication
All calls require a bearer token in the Authorization header. Tokens are compared in constant time (no timing leaks). The service is secure by default: ConoCard refuses to start without a configured token.
Authorization: Bearer your-token-here
Request body (example)
{
"guest": {
"name": "Jane Doe",
"reference": "booking-1234"
},
"validity": {
"from": "2026-06-01T15:00:00Z",
"until": "2026-06-05T11:00:00Z"
},
"printer": "192.168.1.42"
}
Response (JSON)
{
"status": "issued",
"uid": "04A5B6C7D8E9",
"printed_at": "2026-05-30T13:42:18Z",
"registered_in": "2n-access-commander",
"audit_id": "01HXYZ..."
}
Code samples
curl
curl -X POST https://conocard.local/api/issue \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"guest":{"name":"Jane Doe"},"printer":"192.168.1.42"}'
Python (requests)
import requests, os
r = requests.post(
"https://conocard.local/api/issue",
headers={"Authorization": f"Bearer {os.environ['CONOCARD_TOKEN']}"},
json={
"guest": {"name": "Jane Doe"},
"printer": "192.168.1.42",
},
timeout=30,
)
r.raise_for_status()
print(r.json()["uid"])
PHP
// guzzlehttp/guzzle
$client = new GuzzleHttp\Client();
$resp = $client->post('https://conocard.local/api/issue', [
'headers' => ['Authorization' => 'Bearer ' . $token],
'json' => ['guest' => ['name' => 'Jane Doe'], 'printer' => '192.168.1.42'],
]);
$uid = json_decode($resp->getBody(), true)['uid'];
Node.js (fetch)
const res = await fetch("https://conocard.local/api/issue", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.CONOCARD_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ guest: { name: "Jane Doe" }, printer: "192.168.1.42" }),
});
const { uid } = await res.json();
Audit log: JSON-Lines straight to your SIEM
Every issuance — success and failure — is written to an append-only audit log in JSON-Lines format. One line per event, ingestible directly by Splunk, ELK, Microsoft Sentinel or any other SIEM. No parsers, no schema conversions.
{"ts":"2026-05-30T13:42:18Z","event":"issue.success","uid":"04A5B6C7D8E9","printer":"192.168.1.42","source":"api/jane.doe"}
{"ts":"2026-05-30T13:43:02Z","event":"issue.error","reason":"printer_unreachable","printer":"192.168.1.43"}
Multiple printers from one server
One instance can serve several ZC350s concurrently through a shared control plane with source-IP demux. In practice: configure the printer IP per request, or pre-define a mapping in the service config. No one-to-one relationship between server and printer.
For multi-site scenarios it's typical to run one instance per location (in a Linux VM or Windows server), or a central instance with VPN connectivity to the sites. A central multi-site management dashboard is on the roadmap.
ConoCard REST API vs. the Zebra stack
| Aspect | Zebra CardStudio + Silex | ConoCard REST API |
|---|---|---|
| Control path | USB tether per workstation | HTTP over IP |
| OS | Windows only | Linux + Windows |
| Driver install per workstation | Yes | No |
| Authentication | OS user | Bearer token, secure by default |
| Audit trail | Nothing standardised | Append-only JSON-Lines |
| UID readback in the same session | Separate, manual | Inline in the response |
| Multi-printer per host | Not scalable | Source-IP demux |
| Integration with external systems | No API | REST + webhook output |
Frequently asked questions
Do I need a Zebra driver?
No. The ZC350 is driven entirely over IP through a reimplementation of the Silex SX-200 USB-over-IP protocol. No Zebra driver, no CardStudio, no Silex SX Virtual Link.
Which endpoints are exposed?
The primary endpoint is POST /api/issue. In addition there are health and status endpoints (GET /api/health, GET /api/status) for monitoring and service checks.
What if the printer is unreachable?
The call returns a 503 with a structured failure reason (printer_unreachable). The event also lands in the audit log so you can analyse later when and how often this happens.
Can I call from a mobile app?
Yes. The endpoint is HTTP only — anything that speaks HTTP (a mobile app, an Access Commander box, a reception tablet) can request an issuance.
How does this compare to CardStudio?
CardStudio is a per-workstation desktop tool without an API. ConoCard replaces that pattern with one server-side service over IP. See the CardStudio alternative page for the migration angle.