Linque
Linque takes a shopper from a name and a mobile number to a card token in your tenant. One call sends a verification code. One submits it and returns the discovered cards. One checks the selected card's security code and returns the card as a token. The guide covers the flow and the Elements reference covers the SDK.
Linque is available under the Basis Theory API hosts you already use and inherits their networking, rate-limiting, and firewall behavior. All requests and responses use JSON.
| Environment | Base URL |
|---|---|
| Sandbox | https://api.btsandbox.com/linque |
| Production | https://api.basistheory.com/linque |
Every response includes BT-TRACE-ID. Include that value when contacting support about a specific request.
Authentication
There is no separate Linque credential. Every request carries BT-API-KEY with a Basis Theory application key. Linque must be enabled for the tenant; otherwise every request answers 403 with type: "feature_unavailable".
| Permission | Routes | Sufficient credential |
|---|---|---|
linque:verification:create | Send and submit a verification code | Publishable |
linque:credential:read | List and verify discovered cards | Publishable |
The permission says your application may use Linque. It is not what limits a call to one shopper. The credential routes also need to know which shopper is being read.
A publishable key sends the session key that Submit a Verification Code returned, as BT-LINQUE-SESSION. The session key is scoped to that shopper and tenant and expires 30 minutes after it is issued. This is Linque's shopper session, not a Basis Theory session; the two are never interchangeable.
A private key does not need a session key. It names the shopper with the id query parameter, using the id the same call returned. That id is valid for 90 days.
Idempotency
Send a Verification Code and Verify a Card accept BT-IDEMPOTENCY-KEY. A repeated request with the same key and body returns the stored result for 30 minutes instead of performing the operation again. A key whose first request is still running answers 409 with type: "idempotency_key_in_use".
Use an idempotency key when sending a verification code. Without one, retrying after a dropped connection can send another SMS. Use one on the card verification too: a retry under the same key returns the token already created instead of creating a second one. To intentionally send another code, make a new request with a new idempotency key.
Submit a Verification Code is not idempotent. A resubmitted code is checked again. A code that was already accepted answers 410 with type: "verification_code_expired".
Errors
Errors follow Problem Details and are returned as application/problem+json. Branch on type, which is stable. title and detail are written for people and may change. Machine-readable context appears under errors.
{
"type": "security_code_invalid",
"title": "That security code is not right",
"status": 403,
"detail": "That code did not match.",
"instance": "/credentials/acc_abc123/verifications",
"errors": {
"attempts_remaining": 2
}
}
type | Status | Meaning |
|---|---|---|
invalid_request | 400 | A field is missing or malformed. detail names it |
invalid_phone_number | 400 | phone is not valid E.164, is not a United States mobile number, or cannot be reached. errors.phone says which |
unauthorized | 401 | The key is missing or not recognized, a publishable key sent no session key, or the session key has expired |
forbidden | 403 | The application lacks the route's permission, the key type cannot call Linque, or the verification or session belongs to another tenant |
feature_unavailable | 403 | Linque is not enabled for the tenant |
not_found | 404 | The credential does not belong to the shopper named by the session key or id, or id names no shopper |
verification_rate_limited | 429 | Too many send attempts to one number. errors.retry_after is in seconds, and the Retry-After header carries the same value |
verification_code_invalid | 403 | Incorrect code. Another attempt may succeed |
verification_code_expired | 410 | The code has expired or was already used. Send another |
security_code_invalid | 403 | Incorrect security code. errors.attempts_remaining says how many attempts remain |
security_code_attempts_exhausted | 429 | No attempts remain for this card |
credential_unavailable | 409 | The card cannot currently be verified |
idempotency_key_in_use | 409 | The request with this key is still running |
upstream_unavailable | 503 | A provider or the vault did not answer. No attempt was spent; retry |
Send a Verification Code
Sends a one-time verification code to the shopper's mobile number by SMS. Nothing about the shopper is created or readable until the code is submitted successfully.
Requests are limited to three sends per five minutes per mobile number. Resends count against that limit, so a shopper who asks for a second code has one left. A resend is the same request with a new idempotency key.
Permissions
linque:verification:create
Request
curl 'https://api.btsandbox.com/linque/verifications' \
-X 'POST' \
-H 'BT-API-KEY: <PUBLIC_API_KEY>' \
-H 'BT-IDEMPOTENCY-KEY: <IDEMPOTENCY_KEY>' \
-H 'Content-Type: application/json' \
--data '{
"first_name": "Ada",
"last_name": "Morgan",
"phone": "+14155551212"
}'
Request Parameters
| Attribute | Required | Type | Description |
|---|---|---|---|
first_name | true | string | Shopper's given name |
last_name | true | string | Shopper's family name |
phone | true | string | Mobile number in E.164 format, for example +14155551212. United States numbers only |
Response
{
"verification_id": "ver_...",
"sms_sent": true
}
| Property | Type | Description |
|---|---|---|
verification_id | string | Identifier for this verification attempt. Valid for 10 minutes. Pass it to Submit a Verification Code |
sms_sent | boolean | Whether an SMS was actually sent. false for the reserved test numbers |
If sms_sent is false, do not tell the shopper that a message was sent.
Errors
| Status | Type | Meaning |
|---|---|---|
400 | invalid_request | A field is missing or empty |
400 | invalid_phone_number | phone is not valid E.164, is not a United States mobile number, or cannot be reached |
429 | verification_rate_limited | Too many send attempts |
503 | upstream_unavailable | The code could not be sent. Retry |
When rate limited, errors.retry_after and the Retry-After header contain the number of seconds before another attempt should be made.
Every checkout verifies the phone number before discovered cards can be listed, including for shoppers Linque has seen previously.
Submit a Verification Code
Submits the verification code and returns the shopper identifier, a short-lived session key, and the discovered cards. The discovered cards are returned in the same response so the checkout does not need another request immediately after the shopper submits their code.
Permissions
linque:verification:create
Request
curl 'https://api.btsandbox.com/linque/verifications/ver_...' \
-X 'PATCH' \
-H 'BT-API-KEY: <PUBLIC_API_KEY>' \
-H 'Content-Type: application/json' \
--data '{
"code": "123456"
}'
URI Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
verification_id | true | string | The verification_id returned when the code was sent |
Request Parameters
| Attribute | Required | Type | Description |
|---|---|---|---|
code | true | string | Verification code received by the shopper |
Response
{
"session_key": "lqs_...",
"expires_at": "2026-09-23T04:10:00Z",
"id": "ind_...",
"existing": false,
"credentials": [
{
"id": "acc_abc123",
"name": "Chase Sapphire Reserve",
"network": "visa",
"last4": "1580",
"issuer": "Chase",
"art_url": "https://static.example.com/....png",
"requires_security_code": true,
"security_code_length": 3
}
]
}
| Property | Type | Description |
|---|---|---|
session_key | string | Send as BT-LINQUE-SESSION on the credential routes. Scoped to this shopper and issued once |
expires_at | string | When the session key expires, 30 minutes after it is issued |
id | string | Identifier for the shopper. A private key names the shopper with it in ?id= |
existing | boolean | Whether Linque already knew the shopper |
credentials | array | Cards discovered for the shopper, as credential objects |
Errors
| Status | Type | Meaning |
|---|---|---|
403 | verification_code_invalid | Incorrect code; another attempt may succeed |
410 | verification_code_expired | Code has expired or was already used; send another |
503 | upstream_unavailable | The code could not be checked. Retry |
Handle expiration separately from an incorrect code. Telling a shopper that an expired code is merely incorrect encourages them to keep retrying a code that cannot succeed. An unknown or expired verification_id also answers verification_code_expired.
List Credentials
Returns the cards discovered for the shopper represented by the session key. Nothing in this response is sufficient to make a payment. Submit a Verification Code already returns the same list, so this call is for a caller that did not keep it.
Permissions
linque:credential:read
Request
The session key identifies the shopper, so no identifier appears in the path. With a private key instead, name the shopper explicitly with id.
# With a session key
curl 'https://api.btsandbox.com/linque/credentials' \
-H 'BT-API-KEY: <PUBLIC_API_KEY>' \
-H 'BT-LINQUE-SESSION: lqs_...'
# With a private key, naming the shopper
curl 'https://api.btsandbox.com/linque/credentials?id=ind_...' \
-H 'BT-API-KEY: <PRIVATE_API_KEY>'
Query Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
id | conditional | string | The shopper's id. Required when no BT-LINQUE-SESSION header is sent |
Response
{
"credentials": [
{
"id": "acc_abc123",
"name": "Chase Sapphire Reserve",
"network": "visa",
"last4": "1580",
"issuer": "Chase",
"art_url": "https://static.example.com/....png",
"requires_security_code": true,
"security_code_length": 3
}
]
}
| Property | Type | Description |
|---|---|---|
credentials | array | Cards discovered for the shopper, as credential objects |
An empty credentials array is a valid empty wallet, not an error. With the SDK, the wallet renders manual card entry. Without the SDK, offer your own card-entry path.
Errors
Answers unauthorized when the session key is missing or expired, and forbidden when the application lacks the permission.
Verify a Card
Checks the selected card's security code and returns the card as a card token in your tenant. The phone verification proves control of the mobile number. This step proves possession of the selected card.
Attempts are counted per card. Exhausting attempts on one card does not lock the shopper out of the others. Return them to the wallet and let them choose another card.
Permissions
linque:credential:read
Request
curl 'https://api.btsandbox.com/linque/credentials/acc_abc123/verifications' \
-X 'POST' \
-H 'BT-API-KEY: <PUBLIC_API_KEY>' \
-H 'BT-LINQUE-SESSION: lqs_...' \
-H 'BT-IDEMPOTENCY-KEY: <IDEMPOTENCY_KEY>' \
-H 'Content-Type: application/json' \
--data '{
"security_code": "123"
}'
URI Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
credential_id | true | string | The id of a discovered card |
Query Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
id | conditional | string | The shopper's id. Required when no BT-LINQUE-SESSION header is sent |
Request Parameters
| Attribute | Required | Type | Description |
|---|---|---|---|
security_code | conditional | string | Three or four digits. Required unless requires_security_code is false |
Response
{
"token": {
"id": "19e2ca48-8ae4-4276-9fdc-d81f8ce5f2d7",
"type": "card",
"tenant_id": "<YOUR_TENANT_ID>",
"created_by": "<YOUR_APPLICATION_ID>",
"created_at": "2026-10-20T23:20:42Z",
"containers": ["/pci/high/"],
"card": {
"bin": "424242",
"last4": "1580",
"brand": "visa",
"expiration_month": 4,
"expiration_year": 2027
},
"data": {
"number": "XXXXXXXXXXXX1580",
"expiration_month": 4,
"expiration_year": 2027
}
},
"card": {
"brand": "visa",
"last4": "1580",
"expiration_month": 4,
"expiration_year": 2027
}
}
A private key receives the same response with one more top-level property, data:
{
"token": { "id": "19e2ca48-8ae4-4276-9fdc-d81f8ce5f2d7", "type": "card", "...": "..." },
"card": { "brand": "visa", "last4": "1580", "expiration_month": 4, "expiration_year": 2027 },
"data": {
"type": "card",
"number": "4242424242421580",
"expiration_month": 4,
"expiration_year": 2027,
"cvc": "123"
}
}
| Property | Type | Description |
|---|---|---|
token | object | The Basis Theory token created in your tenant from the verified card, exactly as Create Token returns it. Created by your application. Its data is masked, as on any card token |
card | object | Brand, last four digits, and expiration for display or records |
data | object | Private keys only, deprecated. The card itself at the top level of the response: type, number, expiration_month, expiration_year and cvc. It will be removed; do not build on it |
A publishable key never receives the card number: token.data is masked and the response has no top-level data. A private key currently also receives the top-level data shown above. That field is deprecated and will be removed; do not build on it. A retry with the same idempotency key returns the same token rather than creating another.
Errors
| Status | Type | Meaning |
|---|---|---|
400 | invalid_request | security_code is not three or four digits |
403 | security_code_invalid | Incorrect security code |
404 | not_found | The card does not belong to this shopper |
409 | credential_unavailable | Card cannot currently be verified |
429 | security_code_attempts_exhausted | No attempts remain for this card |
503 | upstream_unavailable | The card could not be tokenized. No attempt was spent; retry |
For security_code_invalid, errors.attempts_remaining indicates how many attempts remain.
Credential Object
A card discovered for a shopper. Nothing in it is sufficient to make a payment.
| Property | Type | Description |
|---|---|---|
id | string | Identifier for the discovered card |
name | string | Card-product name when known, otherwise the issuer-provided name |
network | string or null | visa, mastercard, amex, discover, or null when the network is not known |
last4 | string or null | Last four digits |
issuer | string or null | Issuing bank, when known |
art_url | string or null | Card artwork, when the issuer supplies any |
requires_security_code | boolean | Whether the card requires a security code to be verified |
security_code_length | integer or null | Number of digits to collect, or null when the card needs no code |
Use security_code_length as the source of truth. Do not derive the required length from network.