# Partner API terminal integration

The server-to-server surface is `https://api.lp.xyz/v1`. Your consumer needs
access switched on by the team, an `lpx_live_…` key and the required scopes.
Keep the key in your backend secret manager. Requesting access (Settings → API)
and the team's approval are separate from this data API.

## Run the terminal smoke

Use Node 20–22. Inject `LPX_API_KEY` from your secret manager; avoid shell tracing,
printing the environment, or committing credentials. The script sends the key only
in an HTTP header, refuses credential redirects, and reports request IDs.

Download [smoke-public-api.mjs](smoke-public-api.mjs) and
[prepare-signing.mjs](prepare-signing.mjs) into your working directory.

```sh
export LPX_API_URL=https://api.lp.xyz
node smoke-public-api.mjs

# With LPX_API_KEY already injected, optionally exercise an unsigned Base build:
export LPX_FROM=0x1111111111111111111111111111111111111111
export LPX_TO=0x2222222222222222222222222222222222222222
node smoke-public-api.mjs --build
```

The default checks metadata, then an authenticated price and Solana quote when a
key is supplied. `--build` also builds a Base transfer, repeats it with the same
idempotency key and saves `public-api-build.json` with private file permissions.
Use your own public wallet addresses for the deployed canary. The script never
signs or submits a transaction. Every call, including the identical builder
retry, is recorded against your key.

## Read → quote → build

These examples require curl and jq. `$LPX_API_KEY` must already be set. Review the
quote's route, slippage, fees and safety information before requesting a build.
Amounts on swap quotes/builds are **integer base-unit strings**. Transfer/bridge
and LP deposit amounts use **human token units**. Human amounts currently enter
numeric builders; strings that change during numeric conversion are rejected.
Use supported numeric precision and inspect the returned exact wire amounts.

```sh
curl --fail-with-body --silent --show-error "$LPX_API_URL/v1/chains" | jq .
curl --fail-with-body --silent --show-error "$LPX_API_URL/v1/openapi.json" > openapi.json

curl --fail-with-body --silent --show-error --get \
  "$LPX_API_URL/v1/quotes/swap" \
  -H "X-API-Key: $LPX_API_KEY" \
  --data-urlencode chain=solana \
  --data-urlencode inputMint=So11111111111111111111111111111111111111112 \
  --data-urlencode outputMint=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v \
  --data-urlencode amount=1000000 \
  --data-urlencode slippageBps=50 > quote.json

# SOLANA_WALLET is your signer's public base58 address. Preserve the complete raw quote.
jq --arg user "$SOLANA_WALLET" \
  '{chain:"solana",user:$user,quoteResponse:.raw}' quote.json > request.json
IDEMPOTENCY_KEY=$(node -e 'console.log(crypto.randomUUID())')
curl --fail-with-body --silent --show-error \
  "$LPX_API_URL/v1/build/swap" \
  -H "X-API-Key: $LPX_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @request.json > build.json
jq '{signing,metadata,quote,fees,transactions}' build.json
```

An EVM swap build supplies `chain,user,inputMint,outputMint,amount,slippageBps`.
It is requoted on the server. A reviewed `maxValueLossPpm` or
`v4SwapCostApproval` may be required; a `409` means review fresh conditions.
Never automatically acknowledge safety or value-loss fields.

LP requests supply `chain,venue,version,user` plus operation fields. For example:

```sh
jq -n --arg user "$LPX_FROM" --arg pool "$POOL_ADDRESS" \
  '{chain:"base",venue:"Uniswap",version:"CPMM v2",user:$user,pool:$pool,bps:2500}' \
  > remove.json
IDEMPOTENCY_KEY=$(node -e 'console.log(crypto.randomUUID())')
curl --fail-with-body --silent --show-error "$LPX_API_URL/v1/build/lp/remove" \
  -H "X-API-Key: $LPX_API_KEY" -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H 'Content-Type: application/json' --data-binary @remove.json > build.json
```

`bps:2500` withdraws 25%; `10000` is full withdrawal. NFT operations use
`positionIds` instead of a fungible pool address. Use [the OpenAPI contract](https://api.lp.xyz/v1/openapi.json)
for operation fields and [the liquidity guide](https://lp.xyz/docs/liquidity)
for position flows; partner paths use
`/v1/build/lp/…`. Some pool creates are empty (Meteora DLMM, Orca, Raydium CLMM):
wait for the create receipt, take `metadata.poolId`, then separately build/sign
`/v1/build/lp/add` to seed liquidity. Review the returned `seeded` metadata where
available; pool creation alone does not promise a funded position.

## Signing handoff and confirmation

The key authorizes building. Your wallet controls spending and keeps its private
keys. Builders perform ownership checks where the protocol requires them and use
third-party fee policy; an API key does not attest ownership of arbitrary wallets.

- **Solana:** each `encoding:base64` payload is a serialized transaction. Decode
  with `VersionedTransaction.deserialize(Buffer.from(payload,"base64"))` from
  `@solana/web3.js` (supports legacy and versioned messages). Inspect payer,
  instructions, recipients, fees and lookup tables; preserve existing partial
  signatures. Verify the embedded blockhash remains valid on the intended RPC
  cluster. For sequential signing mode, sign/send in array order and wait for
  each receipt. For `signing.mode:sender-bundles` (Helius Sender), sign once while preserving
  partial signatures, then submit each `signing.bundles` transaction-ID group
  as one Helius Sender bundle. Keep the original signed bytes and signatures;
  confirm every transaction before advancing to the next part. An executor must
  support this mode explicitly or reject it before signing. After a timeout or
  uncertain response, reconcile the same signatures with historical lookup;
  observe blockhash expiry before that lookup. Permit a fresh build only after
  the original outcome is established, and recover only the remaining deposit
  slices when earlier slices landed. Never replace a signed blockhash.
- **EVM, sequential:** send each returned call in order with your custody adapter,
  waiting for its receipt and any `payload.expect` allowance before the next call.
  Re-estimate/simulate at execution, review gas and value budgets, and serialize
  nonces across every process using the same wallet. A `feeBearing` action
  settles the action and fee together inside that single call.

Generate reviewable EVM transaction intents locally:

```sh
# PENDING_NONCE accepts the hex result of eth_getTransactionCount(your address,"pending").
node prepare-signing.mjs build.json "$LPX_FROM" "$PENDING_NONCE" \
  > signing-intents.json
```

This tool performs zero network or signing operations. Its nonce values are a
snapshot for review; self-authorization consumes a second nonce on the same EOA. Your signer must refresh state, sign authorization where
required, supply gas/fee parameters, and submit. Persist submitted transaction
hashes before retrying any uncertain send; inspect receipts before rebuilding.

After all required transactions land, save their signatures in the same order
as `transactions` (use `null` for a failed optional step). When a
`confirmationToken` is present:

```sh
jq --slurpfile signatures signatures.json \
  '{confirmationToken,signatures:$signatures[0]}' build.json > confirmation.json
curl --fail-with-body --silent --show-error "$LPX_API_URL/v1/build/confirm" \
  -H "X-API-Key: $LPX_API_KEY" -H 'Content-Type: application/json' \
  --data-binary @confirmation.json | jq .
```

A confirmation token is bound to the consumer, chain, returned fee rows/intents,
and transaction order. It lasts 15 minutes. Tokens authorize receipt verification;
chain evidence determines whether the fee is confirmed. Inspect `confirmed`,
`updated`, `pendingIds`, `pendingReason`, and `createFees` where returned. A `200`
with `confirmed:false`/`updated:false` means verification is still incomplete.
Receipt indexing/finality may require a retry. The general transaction status
endpoint checks a submitted hash/signature; bridge destination fulfillment should
be tracked with the Relay `metadata.bridgeRequestId` through your provider
integration (the public API currently exposes origin transaction status only).

## Retries, cancellation and errors

Builders require a 1–128 character `Idempotency-Key`. Keep the same key, identical
body bytes, and API key when retrying an uncertain HTTP build within ten minutes.
Changing the body returns `409 idempotency_conflict`; an in-flight request returns
`409 idempotency_pending`. Read/confirmation requests always undergo fresh
admission. A replay retains the original build's body requestId while the response
header identifies the current request. Cached builds can outlive Solana blockhash
or EVM quote validity: validate freshness before signing, and use a new key for a
new unsigned plan after reconciling any prior broadcasts.

Authenticated responses use `private,no-store`. `X-Request-Id` is generated by the
server. Admitted calls include `X-RateLimit-Limit` and `X-RateLimit-Remaining`,
admission-time snapshots that concurrent calls can change. Failed handlers keep
their rate-limit cost. Metadata and early denials may omit these headers.

Errors have `{ok:false,error,code,requestId}`. HTTP status remains authoritative.
Actionable handler metadata is retained as `endpointCode`, `reasons`,
`verification`, `riskAcknowledgement`, `reason`, `valueLossPpm`,
`highFeeLastResort`, `hookAddress`, or `retryAfterSec`. Unexpected provider errors
are replaced with a generic message; use the request ID with support.

| Status | Caller action |
| --- | --- |
| 400 | Correct JSON, chain, address, units or operation selectors. |
| 401 | Check the key, its revocation, and whether access is switched on. |
| 403 | Check scope, endpoint availability and source allowlist. |
| 408 / 413 | Upload within 15 seconds and 128 KiB. |
| 409 | Resolve replay conflict/pending, or explicitly review updated safety/quote. |
| 422 | Inspect simulation/signing prerequisite failure; refresh safely. |
| 429 | Honor Retry-After with jitter; allowance is shared across consumer keys. |
| 499 | Caller cancelled; completed provider work may still finish, but the gateway refunds an observed cancellation. |
| 502 / 503 / 504 | Retry with bounded backoff; keep the same builder key/body. |

Redis admission/settlement failures return `503` and fail closed. A reservation
whose final settlement fails is reclaimed by maintenance; the response may be
uncertain, so retry idempotently. Cancelling a build never broadcasts. Some
coalesced provider work may finish after a disconnect.

## Response completeness and endpoint coverage

OpenAPI lists all endpoint paths, scopes, query parameters and body shapes.
Read responses preserve the owning handler's JSON, including additive fields.
Portfolio rows may contain per-source health and completeness metadata. A healthy
source with `complete:false` is partial; a missing field retains legacy complete
semantics. Inspect every source's `ok` and `complete`, plus per-owner failure
fields. `200` alone establishes HTTP success, not complete discovery or a zero
balance. The public gateway forces JSON even when a caller requests NDJSON.

OpenAPI object schemas deliberately
allow additive read fields and venue-specific LP fields; exact response types
remain in their owning server modules. Runtime validators are authoritative for
venue availability and conditional LP requirements.
