Skip to content

VCSP Client usage

Examples for the VCSP client. VCSP stores biometric credentials for you, rather than handing them back to be stored on your side as das-Peak does.

See Supported endpoints for what is covered so far.

Check if the API is alive

from vericlient import VcspClient

client = VcspClient(apikey="your_api_key")

print(f"Alive: {client.alive()}")

Discover what the subscription allows

Before enrolling anyone you need two URNs: a credential configuration, which says what kind of credential to create, and an assurance method, which says what the sample has to satisfy. Both are specific to your subscription, so read them from the service rather than hardcoding them.

from vericlient import VcspClient
from vericlient.vcsp.models import AssuranceMethodInput

client = VcspClient(apikey="your_api_key")

configurations = client.get_credential_configurations().credential_configurations
print(f"Credential configurations: {configurations}")

methods = client.get_assurance_methods().assurance_methods
print(f"Assurance methods: {methods}")

Each assurance method carries a JSON schema describing the values it expects, which is what goes into the assurance field when enrolling:

method = client.get_assurance_method_info(AssuranceMethodInput(urn=methods[0]))
print(method.json_schema.title)
print(method.json_schema.properties)

Enrol a subject

from vericlient import VcspClient
from vericlient.vcsp.models import Applicant, EnrollmentInput

client = VcspClient(apikey="your_api_key")

configuration = next(c for c in client.get_credential_configurations().credential_configurations if "telephone" in c)
method = next(m for m in client.get_assurance_methods().assurance_methods if "enrollment:thresholds" in m)

enrollment = client.enroll_subject(
    EnrollmentInput(
        sample="/path/to/audio.wav",
        applicant=Applicant(
            subject_id="user-1",
            credential_configuration_urn=configuration,
            assurance_method_urn=method,
            assurance={"authenticity_threshold": 0.5},
        ),
    ),
)
print(f"Subject {enrollment.subject_id} now holds credential {enrollment.credential_id}")

subject_id is optional. Leave it out and VCSP generates one, returned on the response.

Enrolling from memory

sample also takes a bytes object. The media type is inferred from the content, but VCSP answers 500 rather than a 4xx when the declared type does not match what it receives, so set content_type explicitly if the sample is anything unusual:

with open("/path/to/audio.wav", "rb") as f:
    enrollment = client.enroll_subject(
        EnrollmentInput(sample=f.read(), applicant=applicant, content_type="audio/wav"),
    )

Read an account and its credentials

from vericlient import VcspClient
from vericlient.vcsp.models import GetAccountInput, GetCredentialInput, GetCredentialsInput

client = VcspClient(apikey="your_api_key")

account = client.get_account(GetAccountInput(subject_id="user-1"))
print(f"Created at {account.created_at}, {len(account.credentials)} credential(s)")

credentials = client.get_all_subject_credentials(GetCredentialsInput(subject_id="user-1")).credentials
for credential in credentials:
    print(f"{credential.id}: {credential.sample.type}, valid until {credential.valid_until}")

one = client.get_credential(
    GetCredentialInput(subject_id="user-1", credential_id=credentials[0].id),
)
print(one.claims, one.tags)

Delete a credential or a whole account

Deleting an account removes every credential it holds. Both are permanent.

from vericlient import VcspClient
from vericlient.vcsp.models import DeleteAccountInput, DeleteCredentialInput

client = VcspClient(apikey="your_api_key")

client.delete_credential(
    DeleteCredentialInput(subject_id="user-1", credential_id="the-credential-id"),
)
client.delete_account(DeleteAccountInput(subject_id="user-1"))

Handling failures

from vericlient.vcsp.exceptions import AccountNotFoundError, AssuranceValidationError

try:
    client.enroll_subject(enrollment_input)
except AssuranceValidationError:
    print("The sample did not meet the assurance thresholds")

try:
    client.get_account(GetAccountInput(subject_id="does-not-exist"))
except AccountNotFoundError:
    print("No such subject")

The full list is in Error handling.

Tags

Tags label credentials so they can be filtered later. A tag must exist before a credential can carry it, and its name must be key:value with alphanumerics only — ^[a-zA-Z0-9]+:[a-zA-Z0-9]+$, so role:employee is valid and role-employee is not.

from vericlient import VcspClient
from vericlient.vcsp.models import CreateTagsInput, DeleteTagInput

client = VcspClient(apikey="your_api_key")

client.create_tags(CreateTagsInput(tags=["role:employee", "region:eu"]))

for tag in client.get_tags().items:
    print(f"{tag.name} (created {tag.created_at})")

client.delete_tag(DeleteTagInput(name="region:eu"))

Once a tag exists, pass it when enrolling:

Applicant(
    subject_id="user-1",
    credential_configuration_urn=configuration,
    assurance_method_urn=method,
    assurance={"authenticity_threshold": 0.5},
    tags=["role:employee"],
)

Groups

Groups are how a set of credentials is referred to for 1:N matching. A group name must match ^[a-zA-Z_][a-zA-Z0-9_]{2,63}$ — letters, digits and underscores, starting with a letter or an underscore. Hyphens are rejected.

from vericlient import VcspClient
from vericlient.vcsp.models import (
    CreateGroupInput,
    DeleteGroupInput,
    GetGroupInput,
    GetGroupMembersInput,
    GetGroupsInput,
)

client = VcspClient(apikey="your_api_key")
configuration = next(c for c in client.get_credential_configurations().credential_configurations if "voice" in c)

group = client.create_group(
    CreateGroupInput(name="support_agents", credential_configuration_urn=configuration),
)
print(f"{group.name} holds {group.size} credentials, expiring {group.expired_at}")

for g in client.get_groups(GetGroupsInput()).items:
    print(g.name)

members = client.get_group_members(GetGroupMembersInput(name="support_agents"))
print(f"{members.total} members")

client.delete_group(DeleteGroupInput(name="support_agents"))

expired_at is a duration going in and a date coming out

CreateGroupInput.expired_at takes an ISO 8601 duration — a retention period such as P1Y or P30D, not a date. The service applies it and answers with the resulting timestamp in CreateGroupOutput.expired_at. Leave it out and credentials are retained for five years.

Subscriptions cap the number of groups

Creating one past the limit raises GroupsLimitExceededError. The limit is part of the subscription, not something the client controls.

Finding credentials across the whole system

get_all_subject_credentials is scoped to one account. list_credentials is not: it walks everything, so filter and page rather than asking for the lot.

from vericlient import VcspClient
from vericlient.vcsp.models import ListCredentialsInput

client = VcspClient(apikey="your_api_key")

page = client.list_credentials(ListCredentialsInput(tags=["role:employee"], size=50))
print(f"{page.total} credentials over {page.pages} pages")
for credential in page.items:
    print(f"{credential.id} belongs to {credential.subject_id}")

Each item carries subject_id, which the per-account endpoints do not return — that is what makes the listing useful for finding an account you only know a tag for.

Retrieving the sample behind a credential

The service answers with the raw bytes it was enrolled with, not with JSON.

from vericlient.vcsp.models import GetCredentialSampleInput

sample = client.get_credential_sample(
    GetCredentialSampleInput(subject_id="user-1", credential_id=credential_id),
)
with open("recovered.wav", "wb") as f:
    f.write(sample.content)
print(sample.content_type)  # audio/wav

Deleting credentials in bulk

from vericlient.vcsp.models import DeleteCredentialsInput

client.delete_credentials(
    DeleteCredentialsInput(group_name="support_agents", delete_empty_accounts=True),
)

Irreversible, and a group is the only filter

Credentials in the group are deleted and removed from every other group they belong to. With delete_empty_accounts, accounts left holding nothing go too. There is no way to scope this by tag or by account.

Inspecting one credential configuration

get_credential_configurations lists the URNs; this returns the schema that an enrolment's claims must satisfy for a given one.

from vericlient.vcsp.models import CredentialConfigurationInput

configuration = client.get_credential_configuration(
    CredentialConfigurationInput(urn="urn:vcsp:credential_configurations:voice_telephone:v1"),
)
print(configuration.claims_schema)

Enrolling in bulk

Batch enrolment is asynchronous: the service accepts the work and hands back a task to follow. The client builds the archive it expects, so you pass applicants rather than assembling a TAR yourself.

from vericlient import VcspClient
from vericlient.vcsp.models import (
    Applicant,
    BatchApplicant,
    BatchEnrollmentInput,
    TaskInput,
)

client = VcspClient(apikey="your_api_key")

batch = client.enroll_batch(
    BatchEnrollmentInput(
        applicants=[
            BatchApplicant(
                sample="/path/to/alice.wav",
                applicant=Applicant(
                    subject_id="alice",
                    credential_configuration_urn=configuration,
                    assurance_method_urn=method,
                    assurance={"authenticity_threshold": 0.5},
                ),
            ),
            BatchApplicant(sample="/path/to/bob.wav", applicant=bob),
        ],
    ),
)

task = client.wait_for_task(TaskInput(task_id=batch.task_id), timeout=300)
if not task.succeeded:
    raise RuntimeError(f"batch finished as {task.status}")

result = client.get_task_result(TaskInput(task_id=batch.task_id)).result
print(result["summary"])  # {'total': 2, 'success': 2, 'error': 0}
for item in result["report"]:
    print(item["subject_id"], item["status"])

A batch can partly succeed: summary counts both outcomes and report carries one entry per applicant, so check the report rather than only the task status.

sample takes a path or bytes, as everywhere else. filename sets the name inside the archive if you need it to be something particular; otherwise it is derived.

Following asynchronous work

Anything that answers 202 runs as a task.

from vericlient.vcsp.models import TaskInput, TaskStatus

# Everything still running or recently finished
for task in client.get_tasks().items:
    print(f"{task.task_id}: {task.status} at {task.progress}%")

# One task, checked once
task = client.get_task(TaskInput(task_id=task_id))
if task.status == TaskStatus.FAILED:
    ...

# Or block until it finishes
task = client.wait_for_task(TaskInput(task_id=task_id), timeout=300, poll_interval=2)
print(task.is_finished, task.succeeded)

client.delete_task(TaskInput(task_id=task_id))

wait_for_task returns on failure as well as on success — check succeeded rather than assuming. It raises TimeoutError if the task is still running when the timeout expires.

status is a plain string

TaskStatus exists to compare against, but the field is typed as str so a state the service adds later does not break deserialisation.

Tasks expire on their own

The service drops a task and its result after thirty days. delete_task is there for callers that would rather not wait.

Matching

Matching is what the stored credentials are for. Pass a SubjectClaimant to check a sample against one subject, or a GroupClaimant to search a whole group.

from vericlient import VcspClient
from vericlient.vcsp.models import GroupClaimant, MatchingInput, SubjectClaimant

client = VcspClient(apikey="your_api_key")
method = "urn:vcsp:assurance_methods:matching:biometric_threshold:v1"

# 1:1 — is this the person they claim to be?
result = client.match(
    MatchingInput(
        sample="/path/to/caller.wav",
        claimant=SubjectClaimant(
            subject_id="alice",
            credential_configuration_urn=configuration,
            assurance_method_urn=method,
            assurance={"biometric_threshold": 0.5},
        ),
    ),
)
print(result.results[0].match_status)  # HIT or MISS
print(result.results[0].biometrics_score)  # 0.0 to 1.0

# 1:N — who in this group is it?
result = client.match(
    MatchingInput(
        sample="/path/to/caller.wav",
        claimant=GroupClaimant(
            group_name="support_agents",
            assurance_method_urn=method,
            assurance={"biometric_threshold": 0.5},
            limit=5,
            filter={"AND": [{"tag": "role:employee"}]},
        ),
    ),
)
print(f"{result.nhits} hits out of {len(result.results)} candidates")

result.sample carries what the service made of the recording — its type, the media type it was read as, and quality figures such as net_speech_duration.

A large 1:N runs asynchronously

The service may accept the work instead of answering. match then returns a TaskCreatedOutput rather than a MatchingOutput, to follow with wait_for_task.

Putting credentials into a group

A credential does not join a group at enrolment: it is added afterwards, by subject or by tag.

from vericlient.vcsp.models import GroupAction, GroupMembershipSource, ModifyGroupInput

# By subject
client.modify_group(
    ModifyGroupInput(
        name="support_agents",
        action=GroupAction.POPULATE,
        from_=GroupMembershipSource(subjects=["alice", "bob"]),
    ),
)

# Or by tag, which scales better
client.modify_group(
    ModifyGroupInput(
        name="support_agents",
        action=GroupAction.POPULATE,
        from_=GroupMembershipSource(tags=["role:employee"]),
        credential_ttl="P30D",
    ),
)

# Taking them out again
client.modify_group(
    ModifyGroupInput(
        name="support_agents",
        action=GroupAction.REMOVE,
        from_=GroupMembershipSource(subjects=["bob"]),
    ),
)

The argument is from_, because from is a reserved word in Python; it is sent as from. A small change answers with the group, a large population is accepted as a task.

Changing a credential's tags

The tags must already exist — create them with create_tags first.

from vericlient.vcsp.models import CredentialTagAction, ModifyCredentialTagsInput

credential = client.modify_credential_tags(
    ModifyCredentialTagsInput(
        subject_id="alice",
        credential_id=credential_id,
        action=CredentialTagAction.ADD,
        tags=["region:eu"],
    ),
)
print(credential.tags)

Clustering a group

Clustering groups similar credentials together, to find duplicates or related enrolments. It runs as a task.

from vericlient.vcsp.models import ClusteringInput, TaskInput

task = client.start_clustering(
    ClusteringInput(
        name="onboarding_faces",
        assurance_method_urn="urn:vcsp:assurance_methods:clustering:thresholds:v1",
        properties={"similarity_threshold": 0.5, "mode": "similarity_based"},
    ),
)
client.wait_for_task(TaskInput(task_id=task.task_id))

Face credentials only

A group of voice credentials is rejected with ClusteringNotSupportedError.

The field is properties, not assurance

Every other endpoint calls the values an assurance method requires assurance. Clustering calls them properties.