Any QR reader
A person opens the AllowID app and shows its QR code. Whatever reads it sends the text to AllowID, and gets back who it is.
What counts as a reader§
Anything that turns a QR code into text:
- A USB or serial barcode scanner. Most behave like a keyboard: they type the text and press Enter. Your program just reads lines.
- A turnstile or access panel with a QR reader that can call a URL or run a script.
- A phone or tablet camera in your own app, using any QR library.
- A webcam on a kiosk PC.
The app's QR code is ordinary text. It starts with PI, followed by ten letters and
digits, and sometimes more after a colon (a signature the app adds). Send all of it as it was
read; you don't need to parse it.
The call§
answer = client.scan(text)
if answer.result == "resolved":
person = answer.subject # same for this person every visit, at your company
if answer.linked:
email = answer.claims["email"]
const answer = await allowid.scan(text);
if (answer.result === "resolved") {
const person = answer.subject; // same for this person every visit, at your company
if (answer.linked) console.log(answer.claims.email);
}
var answer = await allowid.ScanAsync(text);
if (answer.Resolved)
{
var person = answer.Subject; // same for this person every visit, at your company
if (answer.Linked) Console.WriteLine(answer.Claims!["email"]);
}
AllowID.ScanResult answer = allowid.scan(text);
if (answer.resolved()) {
String person = answer.subject(); // same for this person every visit, at your company
if (answer.linked()) System.out.println(answer.claims().get("email"));
}
$answer = $allowid->scan($text);
if ($answer['result'] === 'resolved') {
$person = $answer['subject']; // same for this person every visit, at your company
if ($answer['linked']) echo $answer['claims']['email'];
}
answer, err := client.Scan(ctx, text)
if err != nil {
return err
}
if answer.Resolved() {
person := answer.Subject // same for this person every visit, at your company
if answer.Linked {
fmt.Println(answer.Claims["email"])
}
_ = person
}
allowid_scan_result r;
if (allowid_scan(client, text, &r) == ALLOWID_OK && r.resolved) {
/* r.subject: same for this person every visit, at your company */
char email[256];
if (r.linked && allowid_claim(&r, "email", email, sizeof email)) printf("%s\n", email);
}
allowid_scan_result_free(&r);
auto answer = client.scan(text);
if (answer.resolved()) {
auto person = answer.subject; // same for this person every visit, at your company
if (answer.linked) std::cout << answer.claims["email"] << "\n";
}The answer§
| result | Meaning | Billed |
|---|---|---|
resolved | A current AllowID code. subject says who. | yes |
unknown | Looks like one but is not current: a screenshot from earlier, an expired code, a revoked phone. | no |
invalid | Not an AllowID QR code at all, such as a product barcode. | no |
replayed | This exact code was already used: here more than a minute ago, or at another company. | no |
The same code again from your company within a minute (a reader that reads twice, a person who holds
the phone too long) returns the first answer with repeat: true and is not billed again.
The code in the app changes every 30 seconds, and AllowID accepts it for about a minute and a half. Send it when you read it. Don't queue it to send later.
A complete reader program§
For a keyboard-style USB scanner: read a line, ignore anything that is not an AllowID code, ask AllowID, act on the answer. The shape check runs locally, so a product barcode costs nothing.
import sys
import allowid
client = allowid.Client()
known = {} # subject -> name, in your own database
for line in sys.stdin: # the scanner types a line per QR code
text = line.strip()
if not allowid.is_allowid_code(text):
continue
try:
answer = client.scan(text)
except allowid.AllowIDError as e:
print("AllowID unavailable:", e.code)
continue
if answer.result != "resolved":
print("Not accepted:", answer.result)
elif answer.subject in known:
print("Welcome back,", known[answer.subject])
else:
print("New visitor", answer.subject)
import * as readline from "node:readline";
import { AllowID, AllowIDError, isAllowIdCode } from "@allowid/sdk";
const allowid = new AllowID();
const known = new Map(); // subject -> name, in your own database
for await (const line of readline.createInterface({ input: process.stdin })) {
const text = line.trim();
if (!isAllowIdCode(text)) continue;
try {
const a = await allowid.scan(text);
if (a.result !== "resolved") console.log("Not accepted:", a.result);
else console.log(known.has(a.subject) ? `Welcome back, ${known.get(a.subject)}` : `New visitor ${a.subject}`);
} catch (e) {
console.log("AllowID unavailable:", e instanceof AllowIDError ? e.code : e);
}
}
using AllowID;
using var allowid = new AllowIDClient();
var known = new Dictionary<string, string>(); // subject -> name, in your own database
string? line;
while ((line = Console.ReadLine()) != null)
{
var text = line.Trim();
if (!AllowIDClient.IsAllowIdCode(text)) continue;
try
{
var a = await allowid.ScanAsync(text);
if (!a.Resolved) Console.WriteLine($"Not accepted: {a.Result}");
else if (known.TryGetValue(a.Subject!, out var name)) Console.WriteLine($"Welcome back, {name}");
else Console.WriteLine($"New visitor {a.Subject}");
}
catch (AllowIDException e) { Console.WriteLine($"AllowID unavailable: {e.Code}"); }
}
AllowID allowid = new AllowID();
Map<String, String> known = new HashMap<>(); // subject -> name, in your own database
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
for (String line; (line = in.readLine()) != null; ) {
String text = line.trim();
if (!AllowID.isAllowIdCode(text)) continue;
try {
AllowID.ScanResult a = allowid.scan(text);
if (!a.resolved()) System.out.println("Not accepted: " + a.result());
else if (known.containsKey(a.subject())) System.out.println("Welcome back, " + known.get(a.subject()));
else System.out.println("New visitor " + a.subject());
} catch (AllowIDException e) {
System.out.println("AllowID unavailable: " + e.code());
}
}
$allowid = new \AllowID\Client();
$known = []; // subject => name, in your own database
while (($line = fgets(STDIN)) !== false) {
$text = trim($line);
if (!\AllowID\Client::isAllowIdCode($text)) continue;
try {
$a = $allowid->scan($text);
if ($a['result'] !== 'resolved') echo "Not accepted: {$a['result']}\n";
elseif (isset($known[$a['subject']])) echo "Welcome back, {$known[$a['subject']]}\n";
else echo "New visitor {$a['subject']}\n";
} catch (\AllowID\AllowIDException $e) {
echo "AllowID unavailable: {$e->errorCode}\n";
}
}
client := allowid.New("", "")
known := map[string]string{} // subject -> name, in your own database
sc := bufio.NewScanner(os.Stdin)
for sc.Scan() {
text := strings.TrimSpace(sc.Text())
if !allowid.IsCode(text) {
continue
}
a, err := client.Scan(context.Background(), text)
switch {
case err != nil:
fmt.Println("AllowID unavailable:", allowid.ErrorCode(err))
case !a.Resolved():
fmt.Println("Not accepted:", a.Result)
case known[a.Subject] != "":
fmt.Println("Welcome back,", known[a.Subject])
default:
fmt.Println("New visitor", a.Subject)
}
}
#include <curl/curl.h>
#include <stdio.h>
#include <string.h>
#include "allowid.h"
int main(void) {
curl_global_init(CURL_GLOBAL_DEFAULT);
allowid_client *c = allowid_new(NULL, NULL);
char line[1024];
while (fgets(line, sizeof line, stdin)) {
line[strcspn(line, "\r\n")] = 0;
if (!allowid_is_code(line)) continue;
allowid_scan_result r;
if (allowid_scan(c, line, &r) != ALLOWID_OK)
printf("AllowID unavailable: %s\n", allowid_last_error(c)->code);
else if (!r.resolved)
printf("Not accepted: %s\n", r.result);
else
printf("Visitor %s\n", r.subject);
allowid_scan_result_free(&r);
}
allowid_free(c);
return 0;
}
curl_global_init(CURL_GLOBAL_DEFAULT);
allowid::Client client;
std::map<std::string, std::string> known; // subject -> name, in your own database
for (std::string line; std::getline(std::cin, line); ) {
if (!allowid::is_code(line)) continue;
try {
auto a = client.scan(line);
if (!a.resolved()) std::cout << "Not accepted: " << a.result << "\n";
else if (known.count(a.subject)) std::cout << "Welcome back, " << known[a.subject] << "\n";
else std::cout << "New visitor " << a.subject << "\n";
} catch (const allowid::Error &e) {
std::cout << "AllowID unavailable: " << e.code << "\n";
}
}Knowing who someone is§
A scan tells you which person it is (the subject), but not their name or email, unless that person has once approved your company in the app. The usual pattern:
- First visit: the person signs in once with the app, on your website or on a tablet at the desk
(website sign-in). They see your company's name and approve sharing their
email. You store the
subjectyou get back against your own record of them. - Every visit after: a scan returns the same
subject, so you know who it is. It also carrieslinked: trueand their email.
If you only need to recognise returning people (counting visits, unlocking a locker someone opened earlier), the subject alone is enough and step 1 is not needed.
When the network is down§
The API needs to reach AllowID: a scan cannot be checked offline. Decide what your door or desk
does when network_error comes back. For a door, a fallback such as a receptionist or a
badge is usually better than letting everyone in. AllowID's own access controllers can check the app's
QR code offline for up to 24 hours. If you need that, ask us about an AllowID site controller.