# Share endpoints

Hand a record to another organization, and take delivery of one handed to you.

Authentication, the response envelope, and pagination are on
[API conventions](https://app.expressconsent.com/docs/reference/api). All paths are relative to
`https://api-next.expressconsent.com`.

| | |
|---|---|
| `POST /v1/cdrs/:cdrId/share` | Create a share URL for a record you hold |
| `POST /v1/shares/:token` | Receive a record that was shared with you |
| `GET /v1/shares/:token` | Redirect a person to the dashboard to receive it by hand |

The direction evidence moves and who pays for it is explained on
[access and collection](https://app.expressconsent.com/docs/concepts/access). This page is the wire format.

## POST /v1/cdrs/:cdrId/share

Creates a share URL for a record your organization holds. The body is optional.

```bash
curl -sS -X POST -H "X-API-Key: $EC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expiresInMs": 604800000}' \
  "https://api-next.expressconsent.com/v1/cdrs/cdr_abc123/share"
```

```json
{
  "ok": true,
  "data": {
    "token": "xYz7Qw...",
    "shareUrl": "https://api-next.expressconsent.com/v1/shares/xYz7Qw...",
    "expiresAt": 1741839367890
  },
  "requestId": "3f2504e0-4f89-11d3-9a0c-0305e82c3301"
}
```

You do not need to have collected a record to share it. Sharing a record you have not paid for is
normal for a lead generator whose buyers are the payers.

### Request

**`expiresInMs`** (`number`, optional, default `2592000000`)

How long the share URL stays usable, in milliseconds from now. The default is 30 days. Read
`expiresAt` on the response for the expiry you actually got.

Constraints: Clamped to a maximum of 2 years. A zero, negative, or non-finite value falls back to the default rather than being rejected, as does any non-number.

### Response

**`token`** (`string`, required)

The share token. This is the credential: anyone holding it and an API key for a different
organization can take the record.

**`shareUrl`** (`string`, required)

The absolute URL to hand to the buyer. Identical in form to the `shareUrl` a capture with
`autoShare` returns, so the two paths are interchangeable.

**`expiresAt`** (`number`, required)

When the token stops working, as a Unix timestamp in milliseconds.

Generating the share URL during the capture instead avoids the extra call altogether. See `autoShare`
on [captureCDR()](https://app.expressconsent.com/docs/reference/capture-cdr).

## POST /v1/shares/:token

Receives a shared record into your organization. The body is optional; sent without one, this saves the
record and bills you for it, unless you already have access, or unless the sender had already paid, in
which case their access passes to you at no charge.

```bash
curl -sS -X POST -H "X-API-Key: $EC_API_KEY" \
  "https://api-next.expressconsent.com/v1/shares/xYz7Qw..."
```

```json
{
  "ok": true,
  "data": {
    "claimed": true,
    "alreadyClaimed": false,
    "cdrId": "cdr_abc123",
    "domainId": "example.com",
    "shareEventId": "se_9f2b..."
  },
  "requestId": "3f2504e0-4f89-11d3-9a0c-0305e82c3301"
}
```

### Request

`collect` and `acceptance` are the only fields this body accepts. Anything else is a `400
INVALID_ARGUMENT` naming the offending key. A misspelled `acceptance` must not quietly mean "no
requirements".

**`collect`** (`boolean`, optional, default `true`)

Whether to become the payer for this record. With `false` the record still lands in your account with
its metadata and consent facts, and you are not billed.

Whether you can download the evidence then depends on the sender. If they had already paid, their
access passes to you either way. It is theirs to give, so opting out of payment does not decline it.
If they had not, you have no download access until you collect the record later. So `collect: false`
means *"do not make me the payer"*, not *"do not give me access"*.

Constraints: Any non-boolean value is a 400 INVALID_ARGUMENT rather than being coerced.

**`acceptance`** (`object`, optional)

Requirements the record must meet before you will take it. A record that fails is refused
outright: not saved, not charged, and the token stays usable. Fully documented on
[acceptance criteria](https://app.expressconsent.com/docs/reference/api/acceptance).

### Response

**`claimed`** (`boolean`, required)

Always `true`. The call either succeeds or returns an error.

**`alreadyClaimed`** (`boolean`, required)

`true` when your organization had already received this record through this token. Repeating the
request is safe and never charges twice.

**`cdrId`** (`string`, required)

The record identifier, now readable through
[the CDR endpoints](https://app.expressconsent.com/docs/reference/api/cdrs).

**`domainId`** (`string`, required)

The domain the record is filed under, which you need to list it.

**`shareEventId`** (`string`, required)

Identifies this handoff. **An empty string when `alreadyClaimed` is `true`**: it is not looked up
again on a repeat request.

> **Info: Nothing you send on a repeat request has any effect**
>
> Once your organization has received a record through a token, the endpoint returns early. `acceptance`
> is not evaluated, and `collect: true` after a first request that used `collect: false` neither bills you
> nor grants access. To gain access later, collect the record directly.

### One token, several recipients

A token is not consumed by being used. It is idempotent per organization, so the same token can hand the
same record to several different organizations until it expires. Treat a share token as a bearer
credential and give it only to the buyer it is meant for.

Expiry is checked before that idempotency check, so repeating a request for a record you already hold
returns `410 GONE` once the token expires rather than reporting it was already received.

### You cannot receive your own share

A token created by your organization is refused for your organization with `400 INVALID_ARGUMENT`. To
get download access to a record you captured, collect it. See
[the CDR endpoints](https://app.expressconsent.com/docs/reference/api/cdrs). This is the wall an organization that both generates
and buys leads hits first; [generators and buyers](https://app.expressconsent.com/docs/roles) explains why.

## GET /v1/shares/:token

Redirects to the dashboard, where a person can sign in and take the record by hand. Responds `302` with
a `Location` of `https://app.expressconsent.com/claim/{token}`.

This is the only endpoint that needs no API key, because the person following it authenticates in the
dashboard instead. It exists so that a share URL pasted into an email is a working link for a human
rather than an error.

Nothing is charged by following the redirect. The request that receives the record is the one the
dashboard makes afterwards.

## Errors

Every code, its HTTP status, and what to do about it is on
[API errors](https://app.expressconsent.com/docs/reference/api/errors). The ones specific to these endpoints:

| Status | Code | Cause |
|---|---|---|
| **202** | `CDR_PENDING` | The record has not finished rendering. Retry after the `Retry-After` header says to, which is 2 seconds. Note the 2xx status on a failure. |
| 400 | `INVALID_ARGUMENT` | Your own organization created the token, the body carried an unrecognized field, `collect` was not a boolean, `acceptance` did not validate, or `acceptance.leadMatch.phone` was not a normalizable North American number |
| 403 | `PASSTHROUGH_ORG_CANNOT_CLAIM` | Your organization has no billing set up and cannot receive shared records on any surface. Such an organization normally has no API access either, in which case `API_ACCESS_DISABLED` comes back first. |
| 404 | `NOT_FOUND` | No such token, or no such record on the share path |
| 409 | `CONFLICT` | On the share path, more than one record on our side carries that identifier. A data problem, not something you caused. |
| 410 | `GONE` | The token has expired |
| 410 | `CDR_INVALID` | The record was flagged invalid and cannot be shared or received |
| 422 | `CDR_ACCEPTANCE_FAILED` | The record did not meet your `acceptance` requirements |

`CDR_PENDING` is common immediately after a capture that used `autoShare`: the token is created during
the upload, so a buyer acting on it within a second or two arrives before the visual record exists. Retry
rather than treating it as a failure.
