Release a lien
curl --request POST \
--url https://api.cardinalzyn.com/title/{id}/release \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.cardinalzyn.com/title/{id}/release"
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.cardinalzyn.com/title/{id}/release', 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://api.cardinalzyn.com/title/{id}/release",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.cardinalzyn.com/title/{id}/release"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.cardinalzyn.com/title/{id}/release")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cardinalzyn.com/title/{id}/release")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"status": "ok",
"message": "Lien release processed"
}
Electronic Lien & Title
Release Lien
Release the lien on a title once the account is paid off
POST
/
title
/
{id}
/
release
Release a lien
curl --request POST \
--url https://api.cardinalzyn.com/title/{id}/release \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.cardinalzyn.com/title/{id}/release"
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.cardinalzyn.com/title/{id}/release', 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://api.cardinalzyn.com/title/{id}/release",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.cardinalzyn.com/title/{id}/release"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.cardinalzyn.com/title/{id}/release")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cardinalzyn.com/title/{id}/release")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"status": "ok",
"message": "Lien release processed"
}
Submits a lien release for a title entry. For electronic titles (
The body is
is_elt = true) the release goes to the ELT provider; for paper titles it is handled by Cardinal Gray operations. Progress is tracked on account_status (lien_release_pending → lien_release_complete, or lien_error on a recorded rejection) and elt_data.*.
Send no request body. The release-to recipient comes from the title record Cardinal Gray already holds. Passing an address switches the ELT provider into alternate-recipient mode, which adds postal validation and state-specific recipient rules and is the main source of rejections. If a title needs to go somewhere other than the borrower, update
account_data.loan.release_title_to_address with PATCH /title/{id} before calling this endpoint.What happens
- Cardinal Gray checks that ELT is enabled for your organization and that the title belongs to you.
- Electronic title (
is_elt = true)- Pre-flight: the lien is looked up at the ELT provider. It must be Perfected. If the provider already shows it released, in flight, or held as a paper title, you get a
409, nothing is submitted, and the entry is reconciled to the provider’s view (see below). - The release is submitted with the recipient from the title record:
account_data.loan.release_title_to_addressif set, otherwise the first borrower’s address. - On acceptance
elt_data.release_submitted_atis stamped andaccount_statusbecomeslien_release_pending. The provider processes releases in a nightly batch, so the acknowledgement PDF (elt_data.receipt_lien_release) usually appears later.
- Pre-flight: the lien is looked up at the ELT provider. It must be Perfected. If the provider already shows it released, in flight, or held as a paper title, you get a
- Paper title (
is_elt = false) — nothing is sent to the ELT provider.account_statusbecomeslien_release_pendingand the release is handled by Cardinal Gray operations.
Responses
| Status | Meaning |
|---|---|
200 | Submitted. account_status is now lien_release_pending. |
400 | Title record is missing data needed for the release (no borrower address, no ELT email), or the ELT provider rejected the recipient address (kind: "address", may include suggestedAddress). |
403 | ELT not enabled / no ELT credentials for your organization, or you do not have access to this title. |
404 | Title not found. |
409 | Provider state differs from ours — see below. |
500 / 502 / 503 | Provider rejected the release payload (payload), returned an unclassified error (unknown), or has an account/credential problem (account). |
409 cases
The body is {"error": "...", "ddiLienStatus": "<provider status>"} for pre-flight blocks, or {"error", "code", "kind"} when the provider rejected the release itself.
ddiLienStatus / kind | What Cardinal Gray does | What you should do |
|---|---|---|
Released | Sets account_status = lien_release_complete | Nothing — the lien is already released. |
ReleaseSubmitted, ReleasePending | Sets account_status = lien_release_pending | Nothing — a release is already in flight. |
PaperTitle, PaperTitleSubmitted | Sets is_elt = false | The title is paper; Cardinal Gray operations will handle it. |
null (no provider lien), ddiTitleState mismatch | Nothing | Contact Cardinal Gray. |
kind: state_drift, kind: data_mismatch | Records elt_data.release_rejection_code/_reason/_date, sets account_status = lien_error | Contact Cardinal Gray. |
After the call
account_statusmoveslien_release_pending→lien_release_completewhen the provider confirms the release (state confirmation or title mailed). The confirmation PDF is stored aselt_data.receipt_lien_release_complete.- Subscribers to
title.account_status.*receivetitle.account_status.lien_release_pending, thentitle.account_status.lien_release_complete(ortitle.account_status.lien_errorif a rejection was recorded). See Webhooks. GET /title/{id}returnsaccount_status,elt_data.release_submitted_at, and the receipt documents.
Idempotency
Calling the endpoint again while a release is in flight returns409 (ReleaseSubmitted / ReleasePending) and does not double-submit; after completion it returns 409 with ddiLienStatus: "Released". Retrying on 502 / 503 is safe.
Lien transfer to another lienholder is a separate, state-limited workflow and is not part of the partner-facing API surface. Contact Cardinal Gray if you need it.
Authorizations
bearerAuthbasicAuthcookieAuth
API key or Clerk JWT supplied as Authorization: Bearer
Path Parameters
Unique identifier of the title entry