# P2P On-ramp (https://dev.cosmospay.lat/docs/api/p2p-onramp/)

> Local money in through P2P sellers: the market, a firm quote, and purchase sessions that open a hosted purchase delivered to the address you name.

Base URL: `https://api.cosmospay.lat/cosmos-api`

## Read what can be bought with local money

`GET /v1/onramp/p2p/market`

The currencies a buyer can pay with on the key's network (a `prod` key reads mainnet, a `dev` key the sandbox), each with its payment methods, buy price (local money per USD stablecoin) and minimum, plus the chains a purchase can deliver on (each with its token count) and the token a form starts from. Read from the P2P marketplace and served from memory for up to a minute; a currency without a price, a minimum or a payment method is left out, and so is a chain whose token list cannot be read right now.

Requires the `onramp:read` scope. The shared public key may call it.

**Responses**

- `200` (`P2pMarketEntity`): OK
  - `currencies` (P2pCurrencyEntity[], required): The currencies this side trades right now: each with a price, a minimum and at least one payment method.
  - `networks` (P2pNetworkEntity[], required): The chains an order can name, the default token’s first, then the longest token lists. A chain whose token list could not be read right now is left out.
  - `defaultToken` (P2pDefaultTokenEntity, required): The token a form starts from.
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `500`: InternalError (see Shared responses)
- `502`: UpstreamError (see Shared responses)
- `503`: UpstreamUnavailable (see Shared responses)
- `504`: UpstreamTimeout (see Shared responses)

## List the tokens a purchase can deliver on one chain

`GET /v1/onramp/p2p/tokens`

One chain of the market (`chainId`, one of its `networks`), a page at a time: each token with its contract address, symbol, decimals and USD price. The network’s main assets come first, matched by address (its own coin, its USD stablecoins, the wrapped coin, bitcoin, its token, other stablecoins, DeFi blue chips, liquid staked ether), then every other token in the marketplace’s order. `q` searches by symbol fragment or exact address. A chain the market does not offer right now is `400 validation_failed`.

Requires the `onramp:read` scope. The shared public key may call it.

**Query parameters**

- `take` (number, optional): Page size: how many tokens to return (1 to 200).
- `skip` (number, optional): How many items to skip before this page (offset).
- `chainId` (number, required): EIP-155 id of the chain: a `chainId` of the market’s `networks`.
- `q` (string, optional): A symbol fragment (any case) or a token contract address (exact, any case).

**Responses**

- `200` (`P2pTokenListEntity`): OK
  - `data` (P2pTokenEntity[], required): The tokens on this page.
  - `total` (number, required): Matching tokens, not the page length.
  - `take` (number, required): The page size applied, from the `take` query parameter (default 100).
  - `skip` (number, required): Tokens skipped before this page, from the `skip` query parameter (default 0).
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `500`: InternalError (see Shared responses)
- `502`: UpstreamError (see Shared responses)
- `503`: UpstreamUnavailable (see Shared responses)
- `504`: UpstreamTimeout (see Shared responses)

## Get a firm price for a purchase with local money

`POST /v1/onramp/p2p/quotes`

The P2P marketplace prices the order: the token delivered on `chainId` with fees and the swap taken, the least it can deliver, the rate and the fees, valid until `expiresAt`. Nothing is stored. The order is checked against the market first (currency, payment method, chain, token, decimals and minimum: `400 validation_failed` with the reason). A deployment without the marketplace integrator ids answers `503 misconfigured`; the market and the sessions work without them.

Requires the `onramp:write` scope.

**Request body** (`application/json`, required): `P2pOrderDto`

- `currency` (string, required): Local currency paid (purchase) or received (sale): an `id` from the market of the same route, `GET /v1/onramp/p2p/market` or `GET /v1/offramp/p2p/market`.
- `paymentMethod` (string, required): How the local money is paid (purchase) or received (sale): one of that currency’s `paymentMethods` in the market.
- `chainId` (number, required): EIP-155 id of the chain the token is on: a `chainId` of the market’s `networks`.
- `token` (string, required): The token bought or sold: its contract `address` on `chainId`, from `GET .../tokens?chainId=` of the same route (the zero address is the chain’s own coin).
- `amount` (string, required): What the order puts in, a decimal string: the local money paid for a purchase (at most two decimals, at least the currency’s `minAmount`), the token sold for a sale (at most the token’s decimals, worth at least the currency’s `minAmount` in USD where the token has a USD price).
- `address` (string, required): An EVM account on `chainId`: where a purchase delivers the token, or the account a sale sends it from.

**Responses**

- `201` (`P2pQuoteEntity`): Created
  - `quoteId` (string, required): The marketplace’s quote id.
  - `currency` (string, required): Local currency paid (purchase) or received (sale).
  - `token` (string, required): Symbol of the token bought or sold.
  - `tokenAddress` (string, required): Its contract on `chainId` (EIP-55).
  - `chainId` (number, required): EIP-155 id of the chain it moves on.
  - `amountIn` (string, required): What the order puts in, a decimal string: local money for a purchase, the token for a sale.
  - `amountOut` (string, required): What it gives, fees taken, a decimal string: the token a purchase delivers, the local money a sale pays.
  - `minAmountOut` (string, required): The least the order can deliver once it runs, a decimal string.
  - `exchangeRate` (string, required): Local money per token, a decimal string.
  - `fee` (P2pQuoteFeeEntity, required): The fees included in `amountOut`.
  - `expiresAt` (string, required): When the quote stops being valid.
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `429`: RateLimited (see Shared responses)
- `500`: InternalError (see Shared responses)
- `502`: UpstreamError (see Shared responses)
- `503`: UpstreamUnavailable (see Shared responses)
- `504`: UpstreamTimeout (see Shared responses)

## List the purchases opened with local money

`GET /v1/onramp/p2p/sessions`

The purchases this key's consumer opened on the key's network, newest first, from this service's records (no call to the marketplace). Paginated with `take` (default 100, max 100) and `skip`; answers `{ data, total, take, skip }`, where `total` counts every matching session.

Requires the `onramp:read` scope.

**Query parameters**

- `take` (number, optional): Page size: how many items to return (1 to 100).
- `skip` (number, optional): How many items to skip before this page (offset).

**Responses**

- `200` (`P2pSessionListEntity`): OK
  - `data` (P2pSessionEntity[], required): The sessions on this page.
  - `total` (number, required): Matching rows, not the page length.
  - `take` (number, required): The page size applied, from the `take` query parameter (default 100).
  - `skip` (number, required): Rows skipped before this page, from the `skip` query parameter (default 0).
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `500`: InternalError (see Shared responses)

## Open a purchase with local money

`POST /v1/onramp/p2p/sessions`

Records the order and answers `widgetUrl`, the hosted purchase that takes it: open it in a browser tab or window, where the buyer signs in, verifies their identity and pays a P2P seller. `address` is fixed as the recipient there, so the token can only be delivered to it, on `chainId`. `estimatedAmountOut` is the market price, rounded down (null for a token with no USD price); the widget quotes the firm figure before payment. The order is checked against the market first (`400 validation_failed` with the reason), and opened on the key's network.

Requires the `onramp:write` scope.

**Request body** (`application/json`, required): `P2pOrderDto`

- `currency` (string, required): Local currency paid (purchase) or received (sale): an `id` from the market of the same route, `GET /v1/onramp/p2p/market` or `GET /v1/offramp/p2p/market`.
- `paymentMethod` (string, required): How the local money is paid (purchase) or received (sale): one of that currency’s `paymentMethods` in the market.
- `chainId` (number, required): EIP-155 id of the chain the token is on: a `chainId` of the market’s `networks`.
- `token` (string, required): The token bought or sold: its contract `address` on `chainId`, from `GET .../tokens?chainId=` of the same route (the zero address is the chain’s own coin).
- `amount` (string, required): What the order puts in, a decimal string: the local money paid for a purchase (at most two decimals, at least the currency’s `minAmount`), the token sold for a sale (at most the token’s decimals, worth at least the currency’s `minAmount` in USD where the token has a USD price).
- `address` (string, required): An EVM account on `chainId`: where a purchase delivers the token, or the account a sale sends it from.

**Responses**

- `201` (`P2pSessionEntity`): Created
  - `id` (string, required): Cosmos Pay id of the session.
  - `network` (string, required): `public` (mainnet) or `testnet` (the sandbox), from the key.
  - `side` (string, required): `buy` for a purchase (`/v1/onramp/p2p`), `sell` for a sale (`/v1/offramp/p2p`). One of: `buy`, `sell`.
  - `currency` (string, required): Local currency paid (purchase) or received (sale).
  - `paymentMethod` (string, required): How the local money is paid (purchase) or received (sale).
  - `amount` (string, required): What the order puts in, a decimal string: local money for a purchase, the token for a sale.
  - `token` (string, required): Symbol of the token bought or sold.
  - `tokenAddress` (string, required): Its contract on `chainId` (EIP-55); the zero address is the chain’s own coin.
  - `chainId` (number, required): EIP-155 id of the chain it moves on.
  - `address` (string, required): EIP-55. A purchase: where the token is delivered, fixed in the widget so the buyer cannot change it. A sale: the account the order named as the one it is sent from; the seller connects that wallet in the widget.
  - `estimatedAmountOut` (string, required): About what `amount` gives at the market price when the session was opened, rounded down: the token a purchase delivers, or the local money a sale pays. Null for a token with no USD price. The widget quotes the firm figure before anything moves.
  - `reference` (string, required): The reference the marketplace’s order carries.
  - `widgetUrl` (string, required): Open it in a browser tab or window (not an iframe): the person signs in and verifies their identity there, then pays a P2P seller (purchase) or connects their wallet, sends the token and is paid by a P2P buyer (sale).
  - `createdAt` (string (date-time), required): When the session was opened.
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `429`: RateLimited (see Shared responses)
- `500`: InternalError (see Shared responses)
- `502`: UpstreamError (see Shared responses)
- `503`: UpstreamUnavailable (see Shared responses)
- `504`: UpstreamTimeout (see Shared responses)

## Read a purchase opened with local money

`GET /v1/onramp/p2p/sessions/{id}`

One purchase of this key's consumer on the key's network, from this service's records, with its widget URL. Another consumer's session, one opened on the other network, or a sale, is the 404 of a missing one.

Requires the `onramp:read` scope.

**Path parameters**

- `id` (string, required): Cosmos Pay id of the session.

**Responses**

- `200` (`P2pSessionEntity`): OK
- `400`: ValidationFailed (see Shared responses)
- `401`: Unauthorized (see Shared responses)
- `403`: Forbidden (see Shared responses)
- `404`: NotFound (see Shared responses)
- `500`: InternalError (see Shared responses)

## Shared responses

- **Unauthorized** (`ApiErrorBodyEntity`): `no_authenticated_consumer`: The gateway forwarded no API key identity
  - `statusCode` (number, required): Always the HTTP status of the response itself.
  - `code` (string, required): Stable machine-readable code. Branch on this, never on the message: messages are human-facing and may be reworded; codes are never renamed once published. Each response documents the subset it can return. One of: `insufficient_scope`, `no_authenticated_consumer`, `gateway_required`, `admin_console_only`, `not_found`, `validation_failed`, `payload_too_large`, `idempotency_conflict`, `operation_in_flight`, `invalid_state_transition`, `slippage_exceeded`, `insufficient_balance`, `no_path_found`, `trustline_missing`, `invalid_amount`, `invalid_memo`, `transaction_rejected`, `transaction_already_settled`, `recipient_not_registered`, `payment_transfer_mismatch`, `fee_transfer_mismatch`, `merchant_fee_transfer_mismatch`, `payer_mismatch`, `transaction_not_found`, `expired`, `network_unsupported`, `asset_unsupported`, `merchant_fee_unsupported`, `provider_error`, `provider_unavailable`, `quote_not_found`, `quote_expired`, `kyc_state_invalid`, `kyc_review_required`, `account_disabled`, `payload_expired`, `rate_limited`, `customer_wallet_taken`, `alias_taken`, `alias_name_invalid`, `alias_challenge_invalid`, `alias_signature_invalid`, `alias_recovery_invalid`, `alias_address_conflict`, `wallet_handshake_invalid`, `wallet_verifier_invalid`, `wallet_login_code_invalid`, `wallet_login_code_cooldown`, `wallet_session_invalid`, `wallet_signature_invalid`, `wallet_backup_invalid`, `wallet_backup_limit`, `wallet_account_mismatch`, `wallet_provider_unavailable`, `wallet_return_url_not_allowed`, `wallet_recovery_setup_refused`, `easy_recovery_not_found`, `easy_recovery_already_offered`, `easy_recovery_not_offered`, `easy_recovery_code_invalid`, `easy_recovery_burned`, `plugin_not_installed`, `plugin_consent_mismatch`, `plugin_rejected`, `plugin_quota_exceeded`, `plugin_failed`, `protected_payment_buyer_mismatch`, `protected_payment_signature_invalid`, `misconfigured`, `internal_error`.
  - `error` (string, required): The HTTP reason phrase for `statusCode`.
  - `message` (string | string[], required): Human-readable detail. An array when class-validator rejected the body.
  - `details` (object, optional): Machine-readable facts about this failure, present only on the codes whose example shows it: `easy_recovery_code_invalid` carries `{ "attemptsLeft": 4 }`. Absent everywhere else.
  - `path` (string, required): The path of the request that failed.
  - `timestamp` (string, required): When the envelope was produced, ISO 8601.
- **Forbidden** (`ApiErrorBodyEntity`): `insufficient_scope`: the API key does not hold the scope this route requires. The shared public API key also gets this on every route that is not marked `x-cosmos-public-key`, with a message saying so; more scopes will not help, the key is the wrong one. `gateway_required`: the request did not arrive through APISIX.
- **InternalError** (`ApiErrorBodyEntity`): Unexpected server error. The detail is logged server-side and never returned: there is nothing in the body to branch on beyond the code.
- **UpstreamError** (`ApiErrorBodyEntity`): The provider named in `x-cosmos-upstream` refused the request (`provider_error`) or could not be reached at all (`provider_unavailable`). Nothing was charged; retry is safe with the same `Idempotency-Key`.
- **UpstreamUnavailable** (`ApiErrorBodyEntity`): `provider_unavailable`: the provider in `x-cosmos-upstream` is down or unreachable; the message names it. Retry. `misconfigured`: this deployment is missing configuration the route needs (a provider instance, a fee wallet, the plan header the gateway must forward). Retrying will not help; it is an operator problem.
- **UpstreamTimeout** (`ApiErrorBodyEntity`): The provider in `x-cosmos-upstream` did not answer in time. Whether it acted on the request is unknown. Retry with the same `Idempotency-Key`, never with a fresh one.
- **ValidationFailed** (`ApiErrorBodyEntity`): The body, query or path failed validation. `message` is an array of the individual failures. Routes that reject a *valid* request for a reason of their own (an amount, a memo, a state) document the code for it on their own 400.
- **RateLimited** (`ApiErrorBodyEntity`): The budget in `x-cosmos-rate-limit` is spent for this caller. Honour `Retry-After`. The `ratelimit-*` triple rides along on successful responses too, so a client can pace itself instead of being refused. If the counter store itself is unreachable the guard fails closed and answers `503 provider_unavailable`.
- **NotFound** (`ApiErrorBodyEntity`): No such resource, or it exists and belongs to another consumer. The two are deliberately the same answer: "exists but not yours" is an ownership oracle.
