Skip to main content

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.

EnvironmentBase URL
Sandboxhttps://api.btsandbox.com/linque
Productionhttps://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".

PermissionRoutesSufficient credential
linque:verification:createSend and submit a verification codePublishable
linque:credential:readList and verify discovered cardsPublishable

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
}
}
typeStatusMeaning
invalid_request400A field is missing or malformed. detail names it
invalid_phone_number400phone is not valid E.164, is not a United States mobile number, or cannot be reached. errors.phone says which
unauthorized401The key is missing or not recognized, a publishable key sent no session key, or the session key has expired
forbidden403The application lacks the route's permission, the key type cannot call Linque, or the verification or session belongs to another tenant
feature_unavailable403Linque is not enabled for the tenant
not_found404The credential does not belong to the shopper named by the session key or id, or id names no shopper
verification_rate_limited429Too many send attempts to one number. errors.retry_after is in seconds, and the Retry-After header carries the same value
verification_code_invalid403Incorrect code. Another attempt may succeed
verification_code_expired410The code has expired or was already used. Send another
security_code_invalid403Incorrect security code. errors.attempts_remaining says how many attempts remain
security_code_attempts_exhausted429No attempts remain for this card
credential_unavailable409The card cannot currently be verified
idempotency_key_in_use409The request with this key is still running
upstream_unavailable503A 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.

POST
https://api.btsandbox.com/linque/verifications
Copy

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​

AttributeRequiredTypeDescription
first_nametruestringShopper's given name
last_nametruestringShopper's family name
phonetruestringMobile number in E.164 format, for example +14155551212. United States numbers only

Response​

{
"verification_id": "ver_...",
"sms_sent": true
}
PropertyTypeDescription
verification_idstringIdentifier for this verification attempt. Valid for 10 minutes. Pass it to Submit a Verification Code
sms_sentbooleanWhether 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​

StatusTypeMeaning
400invalid_requestA field is missing or empty
400invalid_phone_numberphone is not valid E.164, is not a United States mobile number, or cannot be reached
429verification_rate_limitedToo many send attempts
503upstream_unavailableThe 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.

PATCH
https://api.btsandbox.com/linque/verifications/{verification_id}
Copy

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​

ParameterRequiredTypeDescription
verification_idtruestringThe verification_id returned when the code was sent

Request Parameters​

AttributeRequiredTypeDescription
codetruestringVerification 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
}
]
}
PropertyTypeDescription
session_keystringSend as BT-LINQUE-SESSION on the credential routes. Scoped to this shopper and issued once
expires_atstringWhen the session key expires, 30 minutes after it is issued
idstringIdentifier for the shopper. A private key names the shopper with it in ?id=
existingbooleanWhether Linque already knew the shopper
credentialsarrayCards discovered for the shopper, as credential objects

Errors​

StatusTypeMeaning
403verification_code_invalidIncorrect code; another attempt may succeed
410verification_code_expiredCode has expired or was already used; send another
503upstream_unavailableThe 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.

GET
https://api.btsandbox.com/linque/credentials
Copy

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​

ParameterRequiredTypeDescription
idconditionalstringThe 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
}
]
}
PropertyTypeDescription
credentialsarrayCards 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.

POST
https://api.btsandbox.com/linque/credentials/{credential_id}/verifications
Copy

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​

ParameterRequiredTypeDescription
credential_idtruestringThe id of a discovered card

Query Parameters​

ParameterRequiredTypeDescription
idconditionalstringThe shopper's id. Required when no BT-LINQUE-SESSION header is sent

Request Parameters​

AttributeRequiredTypeDescription
security_codeconditionalstringThree or four digits. Required unless requires_security_code is false

Response​

Publishable key
{
"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:

Private key
{
"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"
}
}
PropertyTypeDescription
tokenobjectThe 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
cardobjectBrand, last four digits, and expiration for display or records
dataobjectPrivate 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​

StatusTypeMeaning
400invalid_requestsecurity_code is not three or four digits
403security_code_invalidIncorrect security code
404not_foundThe card does not belong to this shopper
409credential_unavailableCard cannot currently be verified
429security_code_attempts_exhaustedNo attempts remain for this card
503upstream_unavailableThe 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.

PropertyTypeDescription
idstringIdentifier for the discovered card
namestringCard-product name when known, otherwise the issuer-provided name
networkstring or nullvisa, mastercard, amex, discover, or null when the network is not known
last4string or nullLast four digits
issuerstring or nullIssuing bank, when known
art_urlstring or nullCard artwork, when the issuer supplies any
requires_security_codebooleanWhether the card requires a security code to be verified
security_code_lengthinteger or nullNumber 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.