> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cardinalgray.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Release Lien

> Release the lien on a title once the account is paid off

Submits a lien release for a title entry. For electronic titles (`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.*`.

> [!WARNING]
>
> **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}`](https://docs.cardinalgray.com/api-reference/v1/patch_title) *before* calling this endpoint.

## What happens

1. Cardinal Gray checks that ELT is enabled for your organization and that the title belongs to you.
2. **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_address` if set, otherwise the first borrower's address.
   * On acceptance `elt_data.release_submitted_at` is stamped and `account_status` becomes `lien_release_pending`. The provider processes releases in a nightly batch, so the acknowledgement PDF (`elt_data.receipt_lien_release`) usually appears later.
3. **Paper title (`is_elt = false`)** — nothing is sent to the ELT provider. `account_status` becomes `lien_release_pending` and 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_status` moves `lien_release_pending` → `lien_release_complete` when the provider confirms the release (state confirmation or title mailed). The confirmation PDF is stored as `elt_data.receipt_lien_release_complete`.
* Subscribers to `title.account_status.*` receive `title.account_status.lien_release_pending`, then `title.account_status.lien_release_complete` (or `title.account_status.lien_error` if a rejection was recorded). See [Webhooks](https://docs.cardinalgray.com/guides/webhooks.md).
* [`GET /title/{id}`](https://docs.cardinalgray.com/api-reference/v1/get_title) returns `account_status`, `elt_data.release_submitted_at`, and the receipt documents.

## Idempotency

Calling the endpoint again while a release is in flight returns `409` (`ReleaseSubmitted` / `ReleasePending`) and does not double-submit; after completion it returns `409` with `ddiLienStatus: "Released"`. Retrying on `502` / `503` is safe.

> [!NOTE]
>
> 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.


## OpenAPI

````yaml POST /title/{id}/release
openapi: 3.0.1
info:
  title: Cardinal Gray OpenAPI
  description: OpenAPI for automating vehicle title work
  license:
    name: MIT
  version: 1.0.0
servers:
  - url: https://api.cardinalzyn.com
    description: Staging
  - url: https://api.cardinalgray.com
    description: Production
security:
  - bearerAuth: []
  - basicAuth: []
  - cookieAuth: []
paths:
  /title/{id}/release:
    post:
      summary: Release a lien
      description: >-
        Submit a lien release for a title entry. Send no request body: the
        release-to recipient is taken from the title record you already hold
        (account_data.loan.release_title_to_address, otherwise the borrower's
        address). For electronic titles (is_elt = true) the release is submitted
        to the ELT provider; for paper titles the entry is queued for Cardinal
        Gray operations. On acceptance account_status becomes
        lien_release_pending and later lien_release_complete once the state
        confirms the release.
      parameters:
        - $ref: '#/components/parameters/id'
      responses:
        '200':
          description: >-
            Release accepted. account_status is now lien_release_pending and,
            for electronic titles, elt_data.release_submitted_at is set.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                      - ok
                  message:
                    type: string
                required:
                  - status
                  - message
              example:
                status: ok
                message: Lien release processed
        '400':
          description: >-
            Invalid input or the title record is missing data needed for the
            release (e.g. no borrower address, no ELT email on the title). For a
            `kind: "address"` ELT rejection the response also includes `code`,
            `kind`, and `suggestedAddress` when the postal service offered one.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LienReleaseError'
        '403':
          description: >-
            ELT is not enabled for your organization, ELT credentials are not
            configured, or you do not have access to this title.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Response'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: >-
            The ELT provider's view of the lien does not allow a release right
            now. Nothing was submitted. When the provider already shows the lien
            as Released, in flight, or held as a paper title, Cardinal Gray
            reconciles the entry (account_status or is_elt) to match and reports
            the provider status in ddiLienStatus. Also returned when the ELT
            provider rejects the release for state drift or a data mismatch
            (kind = state_drift | data_mismatch); those rejections are recorded
            on elt_data and move account_status to lien_error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LienReleaseError'
              examples:
                alreadyReleased:
                  summary: Provider already shows the lien as released
                  value:
                    error: DDI already shows this lien as Released
                    ddiLienStatus: Released
                inFlight:
                  summary: A release is already in flight
                  value:
                    error: A lien release is already in flight on DDI
                    ddiLienStatus: ReleaseSubmitted
                paperTitle:
                  summary: Provider holds a paper title
                  value:
                    error: >-
                      DDI holds a paper title for this lien; an electronic
                      release is not available
                    ddiLienStatus: PaperTitle
                rejected:
                  summary: Provider rejected the release
                  value:
                    error: 'Error releasing lien: Lien has already been released'
                    code: '0803'
                    kind: state_drift
        '500':
          description: >-
            Unexpected error, or the ELT provider rejected the release payload
            (kind = payload). Payload rejections are recorded on elt_data and
            move account_status to lien_error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LienReleaseError'
        '502':
          description: >-
            The ELT provider returned an unclassified error (kind = unknown) or
            the lien lookup failed. Unclassified rejections are recorded on
            elt_data and move account_status to lien_error. Safe to retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LienReleaseError'
        '503':
          description: >-
            ELT provider account or credential problem (kind = account). Nothing
            is recorded on the title. Contact Cardinal Gray.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LienReleaseError'
components:
  parameters:
    id:
      name: id
      in: path
      required: true
      schema:
        type: string
      description: Unique identifier of the title entry
  schemas:
    LienReleaseError:
      type: object
      description: >-
        Error body returned by POST /title/{id}/release. `error` is always
        present; the other fields depend on the failure class.
      properties:
        error:
          type: string
          description: Human-readable error message
        ddiLienStatus:
          type: string
          nullable: true
          description: >-
            ELT provider lien status that blocked the release (409 pre-flight),
            e.g. Released, ReleaseSubmitted, ReleasePending, PaperTitle. null
            when the provider has no lien on file for this title.
        ddiTitleState:
          type: string
          description: >-
            Present when the ELT provider's title state differs from the title
            entry's state (409)
        code:
          type: string
          description: ELT provider error code when the provider rejected the release
        kind:
          type: string
          enum:
            - address
            - state_drift
            - data_mismatch
            - payload
            - account
            - unknown
          description: >-
            Classification of a provider rejection: address (400), state_drift /
            data_mismatch (409), payload (500), account (503), unknown (502)
        suggestedAddress:
          $ref: '#/components/schemas/Address'
      required:
        - error
    Response:
      type: object
      properties:
        error:
          type: string
          description: Error message
        statusCode:
          type: number
          description: >-
            HTTP status code. Present on selected success responses, such as
            DELETE /title/{id}.
        disallowedFields:
          type: array
          items:
            type: string
          description: >-
            Fields rejected by PATCH /title/{id} when attempting to modify
            restricted top-level fields
    Address:
      type: object
      properties:
        recipient_name:
          type: string
          description: Name of the recipient at this address
        street1:
          type: string
          description: Primary street address line
        street2:
          type: string
          description: Secondary street address line (apartment, unit, etc.)
        city:
          type: string
          description: City name
        county:
          type: string
          description: County name
        state:
          type: string
          description: State abbreviation
        zip:
          type: string
          description: ZIP/postal code
      required:
        - street1
        - city
        - state
        - zip
  responses:
    NotFound:
      description: Not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Response'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: 'API key or Clerk JWT supplied as Authorization: Bearer <token>'
    basicAuth:
      type: http
      scheme: basic
      description: >-
        Legacy API key authentication supplied with Authorization: Basic
        <api_key>
    cookieAuth:
      type: apiKey
      in: cookie
      name: __session
      description: Clerk session cookie authentication

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.