Store Paycard (Card Migration)
curl --request POST \
--url https://service.pcibooking.net/api/payments/paycard \
--header 'Content-Type: <content-type>' \
--data '
{
"BankCardDetails": {}
}
'import requests
url = "https://service.pcibooking.net/api/payments/paycard"
payload = { "BankCardDetails": {} }
headers = {"Content-Type": "<content-type>"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': '<content-type>'},
body: JSON.stringify({BankCardDetails: {}})
};
fetch('https://service.pcibooking.net/api/payments/paycard', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://service.pcibooking.net/api/payments/paycard",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'BankCardDetails' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: <content-type>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://service.pcibooking.net/api/payments/paycard"
payload := strings.NewReader("{\n \"BankCardDetails\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "<content-type>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://service.pcibooking.net/api/payments/paycard")
.header("Content-Type", "<content-type>")
.body("{\n \"BankCardDetails\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://service.pcibooking.net/api/payments/paycard")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = '<content-type>'
request.body = "{\n \"BankCardDetails\": {}\n}"
response = http.request(request)
puts response.read_body{
"Type": "Visa",
"Number": "491891******5005",
"NameOnCard": "Juan Dela Cruz",
"ExpirationDate": {
"Month": "07",
"Year": "2020"
},
"IssueNumber": "2"
}
Empty response body.
Authentication failed: the API key, session token or access token is missing or was not accepted.
See "Authentication and Permission Failures" on the Error Handling page.
{
"code": -1003,
"message": "You are not authorized to access this resource. Please contact customer support.",
"moreInfo": "Merchant or Owner are not associated with bank card [{{Token}] userID: SoBookIt",
"errorList": null
}
Capture Cards
Store Paycard (Card Migration)
Store card details in PCI Booking when migrating from local storage. The card data is sent in XML format.
POST
/
api
/
payments
/
paycard
Store Paycard (Card Migration)
curl --request POST \
--url https://service.pcibooking.net/api/payments/paycard \
--header 'Content-Type: <content-type>' \
--data '
{
"BankCardDetails": {}
}
'import requests
url = "https://service.pcibooking.net/api/payments/paycard"
payload = { "BankCardDetails": {} }
headers = {"Content-Type": "<content-type>"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': '<content-type>'},
body: JSON.stringify({BankCardDetails: {}})
};
fetch('https://service.pcibooking.net/api/payments/paycard', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://service.pcibooking.net/api/payments/paycard",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'BankCardDetails' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: <content-type>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://service.pcibooking.net/api/payments/paycard"
payload := strings.NewReader("{\n \"BankCardDetails\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "<content-type>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://service.pcibooking.net/api/payments/paycard")
.header("Content-Type", "<content-type>")
.body("{\n \"BankCardDetails\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://service.pcibooking.net/api/payments/paycard")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = '<content-type>'
request.body = "{\n \"BankCardDetails\": {}\n}"
response = http.request(request)
puts response.read_body{
"Type": "Visa",
"Number": "491891******5005",
"NameOnCard": "Juan Dela Cruz",
"ExpirationDate": {
"Month": "07",
"Year": "2020"
},
"IssueNumber": "2"
}
Empty response body.
Authentication failed: the API key, session token or access token is missing or was not accepted.
See "Authentication and Permission Failures" on the Error Handling page.
{
"code": -1003,
"message": "You are not authorized to access this resource. Please contact customer support.",
"moreInfo": "Merchant or Owner are not associated with bank card [{{Token}] userID: SoBookIt",
"errorList": null
}
Card Migration Guide
Migrate existing card data into PCI Booking tokens
Sending raw card data through this endpoint puts your system in PCI DSS scope, since it requires handling raw card numbers to call it. This endpoint is intended for a one-time migration from a system that is already PCI DSS compliant - it is not recommended as an ongoing capture method for production traffic. Use a card capture method that keeps card data off your systems instead. See Card Migration for the full guidance.
This endpoint accepts card details in XML format only. Set
Content-Type: text/xml.Error Responses
| Code | HTTP Status | Condition |
|---|---|---|
| -123 | 400 | Request body is not valid XML or is null |
| -123 | 400 | XML fails schema validation |
| -160 | 404 | Submitted data is PaymentInfo instead of BankCard |
| -179 | 400 | Card validation failed (Luhn check, expiration, or CVV). Returns errorList with details |
| -125 | 400 | Card storage failed due to invalid data |
| -150 | 500 | Card storage failed due to a system error |
| none | 401 | Authentication failed. The response body is empty. See Authentication and Permission Failures |
| -1003 | 403 | Missing CanTokenize permission. message names the reason |
Error detail
Each condition below gives the exactmoreInfo text, why it happens and how to resolve it. The full set is on the Error Handling page.
-123 Message badly formatted - body is not valid XML for this endpoint
-123 Message badly formatted - body is not valid XML for this endpoint
HTTP status: Reason. The endpoint expects XML that validates against its schema, and the body either did not parse as XML or failed validation. When validation is the cause, the failing details are listed in
400message: Message badly formattedmoreInfo:Bad XML document
Bad XML document. Errors: <validation errors>
moreInfo.How to resolve.- Read the list in
moreInfo. It names the elements that failed, which is usually enough on its own. - Check element order. The schema is sequence-sensitive, so correctly named elements in the wrong order still fail.
- Check the declared encoding matches what you actually sent.
-125 Bad input data - expiration date rejected
-125 Bad input data - expiration date rejected
HTTP status: Reason. The expiration month or year is missing, out of range, or in a format the endpoint does not accept.How to resolve.
400message: Bad input datamoreInfo:Expiration year/month error
- Send the month as two digits,
01through12. - Check the year format the endpoint expects. Some accept two digits and some four, and they are not interchangeable.
- Check the date is not in the past. An expired card is rejected at tokenization.
-179 Bad input parameter - CVV format rejected
-179 Bad input parameter - CVV format rejected
HTTP status: Reason. The CVV was present but did not match three or four digits. The API applies the same rule to every card brand.How to resolve.
400message: Bad input parametermoreInfo:Invalid CVV value: <value>
- Send three or four digits, digits only, with no spaces or punctuation. The API accepts either length for any brand - it does not require four digits for American Express, and it does not reject four digits on other brands.
- Omit the field entirely rather than sending an empty string. An absent or empty CVV is accepted, and the card is simply stored without one.
The hosted card entry form is stricter than the API. Where it has detected the brand it accepts exactly three digits for every brand except American Express, which takes three or four; where the brand is unknown it accepts three or four. A CVV that the form rejects would therefore have been accepted had you sent it to the API directly.
401 with an empty body - authentication failed
401 with an empty body - authentication failed
HTTP status:
401Response body: empty. There is no code, message or moreInfo.Reason. PCI Booking could not identify the caller. The response is the same for every cause:- The
Authorizationheader (or thex-pcibooking-api-keyheader) is missing, badly formatted, or has noAPIKEYkeyword. - The API key is not valid, or it belongs to the other environment (sandbox or production).
- The
sessionTokenis not valid or has expired. - The
accessTokenis not valid, has expired, has an expiration time further ahead than the maximum allowed, or was already used. An access token works once only, so reloading a page that uses it, such as a card display iframe, also returns this response.
- Confirm the
APIKEYprefix is present and there is a single space between it and the key. - Confirm the key belongs to the same environment as the host you called. Sandbox and production keys are not interchangeable.
- If you use a session token, check it has not expired and start a new session if it has.
- If you use an access token, generate a new one for every request, including every reload of an iframe.
- Check that your HTTP client does not drop the
Authorizationheader on a redirect. Most clients remove it after a 301 or 302.
Parameter Constraints
| Parameter | Type | Required | Constraints |
|---|---|---|---|
| validateLuhn | boolean | No | Defaults to true. Validates card number with Luhn algorithm |
| validateExpiration | boolean | No | Defaults to false. When true, rejects expired cards |
| saveCVV | boolean | No | Defaults to false. Requires CVV retention policy to be set after storage |
| eliminateCardDuplication | boolean | No | Defaults to false. When true, returns existing token if card already stored (same card number + expiration date) |
| Content-Type | string | Yes | Must be text/xml |
| Auth | string | Yes | ApiKey, AccessToken, or SessionToken |
BankCardDetails XML document. See Card Data XML Structure for the full field reference.
<BankCardDetails xmlns="http://www.pcibooking.net/reservation" schemaVersion="1.0">
<BankCard>
<Type>Visa</Type>
<Number>4918914107195005</Number>
<NameOnCard>Juan Dela Cruz</NameOnCard>
<ExpirationDate>
<Month>07</Month>
<Year>2020</Year>
</ExpirationDate>
<IssueNumber>2</IssueNumber>
<OwnerID>123456789</OwnerID>
<CVV>123</CVV>
</BankCard>
</BankCardDetails>
Location response header. The response body contains the submitted card details with sensitive data masked.
Parameters
Authentication
API key, access token or session token. Use the API key for server-to-server calls, and an access token or a session token when the call is made from a browser. Send one of the three.string
Your API key prefixed with
APIKEY. Example: APIKEY your-api-key. The x-pcibooking-api-key header is also accepted. See the Authentication guide.string
Generated on your side. Single use, and valid for up to 72 hours. How to generate.
string
Returned by an API call. Valid for 5 minutes, and can be used more than once within that time. How to generate.
Headers
string
default:"text/xml"
required
Must be
text/xml. This endpoint accepts card details in XML format only.Query String
string
A reference value that can be used to query for this card token.
string
User ID of the property to associate the token with. Found under “Property settings” in the user’s site. Property Management only. Property Management is closed to new accounts; see Property Management.
string
User ID of the PCI Booking customer (booker ID) to associate the token with. The customer must share their PCI Booking user ID with you.
boolean
default:"false"
Whether to save the CVV in the database. true - save the CVV. false - do not save the CVV.
boolean
default:"false"
When
true, PCI Booking checks whether this card already exists as a token in your account (same card number + expiration date) and returns the existing token instead of creating a new one.boolean
default:"false"
Whether to check if the expiration date is valid.
True validates the expiration date and tokenization will fail if expired. False accepts the date as is, even if in the past.boolean
default:"true"
Whether to check if the card number passes the Luhn algorithm.
True validates using Luhn and tokenization will fail if the number does not pass. False accepts the number as is.string
The region where the card data is stored. One of:
US, IN, AU, JP, CA, IE, GB, BR. If omitted, your account’s default region is used, or Ireland if your account has no default. See Card Storage Regions.Request Body
object
The BankCardDetails object. See the XML structure and example above.
Request Example
Store a Visa card and associate it with a reference and property:- curl
- Node.js
- Python
curl -X POST "https://service.pcibooking.net/api/payments/paycard?ref=booking-12345&merchant=hotel-sunrise&saveCVV=true" \
-H "Authorization: APIKEY your-api-key" \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0" encoding="utf-8"?>
<BankCardDetails xmlns="http://www.pcibooking.net/reservation" schemaVersion="1.0">
<BankCard>
<Type>Visa</Type>
<Number>4111111111111111</Number>
<NameOnCard>Jane Smith</NameOnCard>
<ExpirationDate>
<Month>12</Month>
<Year>2028</Year>
</ExpirationDate>
<CVV>123</CVV>
</BankCard>
</BankCardDetails>'
const xmlBody = `<?xml version="1.0" encoding="utf-8"?>
<BankCardDetails xmlns="http://www.pcibooking.net/reservation" schemaVersion="1.0">
<BankCard>
<Type>Visa</Type>
<Number>4111111111111111</Number>
<NameOnCard>Jane Smith</NameOnCard>
<ExpirationDate>
<Month>12</Month>
<Year>2028</Year>
</ExpirationDate>
<CVV>123</CVV>
</BankCard>
</BankCardDetails>`;
const params = new URLSearchParams({
ref: "booking-12345",
merchant: "hotel-sunrise",
saveCVV: "true"
});
const response = await fetch(
`https://service.pcibooking.net/api/payments/paycard?${params}`,
{
method: "POST",
headers: {
"Authorization": "APIKEY your-api-key",
"Content-Type": "text/xml"
},
body: xmlBody
}
);
const tokenUri = response.headers.get("Location");
console.log("Token URI:", tokenUri);
const data = await response.json();
console.log(data);
import requests
xml_body = """<?xml version="1.0" encoding="utf-8"?>
<BankCardDetails xmlns="http://www.pcibooking.net/reservation" schemaVersion="1.0">
<BankCard>
<Type>Visa</Type>
<Number>4111111111111111</Number>
<NameOnCard>Jane Smith</NameOnCard>
<ExpirationDate>
<Month>12</Month>
<Year>2028</Year>
</ExpirationDate>
<CVV>123</CVV>
</BankCard>
</BankCardDetails>"""
response = requests.post(
"https://service.pcibooking.net/api/payments/paycard",
params={
"ref": "booking-12345",
"merchant": "hotel-sunrise",
"saveCVV": "true"
},
headers={
"Authorization": "APIKEY your-api-key",
"Content-Type": "text/xml"
},
data=xml_body
)
token_uri = response.headers.get("Location")
print("Token URI:", token_uri)
print(response.json())
Location response header (e.g. Location: https://service.pcibooking.net/api/payments/paycard/555fd7b49f134b42a5dbe4d576b2e527).
Response
201 - Card stored. ALocation header is returned with the token URI. The response body contains the card details with sensitive data masked.
Remember to set the CVV Retention Policy on the token if you stored the CVV.
{
"Type": "Visa",
"Number": "491891******5005",
"NameOnCard": "Juan Dela Cruz",
"ExpirationDate": {
"Month": "07",
"Year": "2020"
},
"IssueNumber": "2"
}
Empty response body.
Authentication failed: the API key, session token or access token is missing or was not accepted.
See "Authentication and Permission Failures" on the Error Handling page.
{
"code": -1003,
"message": "You are not authorized to access this resource. Please contact customer support.",
"moreInfo": "Merchant or Owner are not associated with bank card [{{Token}] userID: SoBookIt",
"errorList": null
}

