Myela docs

Myela Payments API

Version v1 — one API, whichever provider settles the money. 44 endpoints across 9 sections.

Overview

Which payment provider settles a merchant's money is a property of their account, never of the request. The same request body works whichever provider is behind it, and moving a merchant between providers requires no change on their side. Testing another provider is a change of key — never of request, URL or environment.

Start with GET /v1/capabilities. It returns every operation and whether it is active, unavailable (your key lacks the entitlement) or not_supported (the provider cannot do it). Branch on that at integration time rather than discovering a 403 in production.

Authentication

Send a bearer token on every request:
Authorization: Bearer myela_sk_test_…
Keys are issued by Myela and shown once. They are stored hashed and cannot be recovered; a revoked key stops working immediately.

A test key authenticates with the bearer token alone. A live key must additionally HMAC-sign every request — an unsigned live key is refused with 401 This key requires request signing. That is why curl and Postman are fine for test keys and unsuitable for live ones.
KeyPrefixLivesCan
Secretmyela_sk_your servereverything, including moving money
Publishablemyela_pk_the browserstart a checkout session and exchange a token — nothing else, and only from an origin on its allow-list

Never put a secret key in a browser, a mobile app, or a repository.

Collections

Every route on this page, ready to run. Both are generated from the same route table as the reference below, so they cannot describe an endpoint that no longer exists.

No credentials are included. Each ships a MYELA_API_KEY placeholder — paste a test key from Settings → API. A live key additionally requires request signing, which neither tool does out of the box.

Both default to https://payments-api-sandbox.merchantservicedepot.com. Point BASE_URL at your own host to run them elsewhere.

Money

Amounts are integers in the currency's minor unit. 4999 is $49.99.

Sending 49.99 is rejected rather than coerced — a float that silently becomes 4999 today becomes 4998 the day it arrives from an arithmetic expression, and that is a settlement discrepancy nobody traces back to the API.

Card capture

No endpoint accepts a card number, and none ever will. Cards are captured in the browser by a Myela-provided field that posts directly to the payment provider — your servers never see a PAN, which is what keeps your integration in PCI SAQ A.

Your page mounts the card field with a publishable key; the shopper types their card; you receive a single-use paymentToken; your server sends that token to POST /v1/payments to charge it, or POST /v1/payment_methods to store it. A token is single-use and short-lived — mint a fresh one per operation.

Myela Elements

Server contract live; hosted frame not yet. The two routes below — POST /v1/checkout_sessions and POST /v1/tokens — are deployed and can be called today, and they are documented in the collections above. What is not serving yet is js.myela.com, so the middle step (mounting the fields and getting a provider token) has no hosted implementation. Until it does, capture a card with the field your Myela contact provides and exchange the result at POST /v1/tokens, or send it straight to POST /v1/payments as a paymentToken.

A session can only start from an origin you have registered. Your publishable key carries an allow-list you manage. It starts EMPTY, and an empty list refuses every origin — a checkout that returns 401 until you add your domain is recoverable; one open to the whole internet is not.

Myela Elements is the browser half of the integration: it renders the card field, posts the card straight to the payment provider, and hands your page a single-use token. Your servers never see a card number.
<script type="module">
  import { Myela } from 'https://js.myela.com/v1/elements.js';

  const myela = Myela({ publishableKey: 'myela_pk_test_…' });
  const card = myela.elements().create('card');

  await card.mount('#card-field');
  card.on('change', (e) => (payButton.disabled = !e.complete));

  payButton.addEventListener('click', async () => {
    const { token, error } = await card.tokenize();
    if (error) return showError(error.message);

    // token is opaque: "mtok_…". Send it to YOUR server.
    await fetch('/checkout', { method: 'POST', body: JSON.stringify({ token }) });
  });
</script>
Then, server-side, the token is just a paymentToken:
await myela.payments.create({ amount: 4999, currency: 'USD', paymentToken: token });
Use the publishable key here (myela_pk_), never the secret one. A publishable key is meant to be readable by anyone who views the page; a secret key in a browser publishes your ability to move money to every visitor, and Elements rejects one outright.

The token is single-use and expires in about two minutes. Mint a fresh one per payment.

Playground

The SDK ships a playground so you can watch the whole flow — request shapes, headers, retries, idempotency, error classes — without touching a card or a live account.
bun install
bun run playground     # → http://localhost:4477
One process doing three jobs: the UI, an in-memory mock of this API, and scenarios that drive the real SDK against that mock over real HTTP. So what you observe is the SDK's genuine behaviour rather than a description of it.

Nothing there can move money. No credentials, no network calls beyond loopback, no settlement.

Test tokens select the outcome, because you cannot rehearse a decline by waiting for a real card to be declined: tok_ok authorizes in full, tok_partial authorizes half the requested amount (status partially_authorized), tok_insufficient declines with a soft error a retry may clear.

The mock is a third dialect. Its outcomes are its own — they do not reproduce against a real provider's sandbox, where the card number or the request decides. Use it to learn the SDK's shape, never to predict what a specific card will do.

Pagination

Every list is cursor-paginated.
GET /v1/customers?limit=25
GET /v1/customers?limit=25&startingAfter=cus_abc123
{ "object": "list", "data": [ … ], "hasMore": true, "nextCursor": "cus_xyz789" }
limit defaults to 10 and caps at 100. Follow nextCursor by passing it as startingAfter. It is present only when hasMore is true, so a loop that stops on hasMore always terminates. A cursor naming no row is a 422, never a silent restart at page one.

Errors

Every error has the same shape, at every status:
{ "error": { "code": "invalid_request", "message": "…" } }
Branch on code, never on message. Codes are a contract; messages are prose and get improved.

An object belonging to another merchant is 404, never 403 — confirming it exists would leak that it exists. 501 is a permanent refusal: do not retry it, check capabilities instead.
HTTPcodeMeans
401authentication_failedmissing, malformed, revoked, or unsigned live key
403permission_deniedvalid key, but not permitted to perform that operation
403capability_not_enabledthe operation exists but is not enabled for your account — the capability field names it. Ask your Myela contact to enable it; unlike 501 this is not permanent
404not_foundno such object on your account
409conflictillegal state transition, e.g. voiding a settled payment
422invalid_requestthe request is wrong — bad amount, unknown cursor, missing field
500api_errorour fault; safe to retry
501capability_not_supportedpermanent refusal — do not retry

Idempotency

Send Idempotency-Key: <unique-string> on any money-moving request. A retry with the same key returns the original result instead of charging twice. Use a fresh key per logical operation, not per retry.

Start here

Run this first. It answers what this key may do, and it is the only route that is not entitlement-gated — asking what you may do must never itself require permission.

GET/v1/capabilities

Capabilities

Every operation, and whether it is active, unavailable (this key lacks the entitlement) or not_supported (the account's provider cannot do it). Branch on this rather than discovering a 403 in production.

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/capabilities" \
  -H "Authorization: Bearer $MYELA_API_KEY"

Payments

The money-moving surface. Amounts are integers in the minor unit — 4999 is $49.99. A decimal is rejected rather than silently charged as a different figure. A payment is created against exactly ONE funding source: a paymentToken from browser capture, or a stored paymentMethodId. Sending both is refused, because which card was charged is not a question to answer afterwards.

POST/v1/paymentspayments.create

Create payment (sale)

capture: true authorizes and captures in one call. Set an Idempotency-Key header on retries: the same key returns the original result rather than charging twice. Amounts are integers in the minor unit — 4999 is $49.99. A decimal is rejected rather than silently charged as a different figure.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/payments" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "amount": 1299,
  "currency": "USD",
  "paymentToken": "{{PAYMENT_TOKEN}}",
  "capture": true
}'
POST/v1/paymentspayments.create

Create payment (auth only)

Holds funds without taking them. Capture it later, or void it. An uncaptured authorization expires on the provider's schedule, not ours.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/payments" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "amount": 2500,
  "currency": "USD",
  "paymentToken": "{{PAYMENT_TOKEN}}",
  "capture": false
}'
POST/v1/payments/{auth_id}/capturepayments.capture

Capture a payment

Only valid on an open authorization. Omit amount to capture the full authorized amount. Whether a PARTIAL capture is permitted depends on the provider behind the account — check capabilities rather than assuming.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/payments/{auth_id}/capture" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "amount": 2500
}'
POST/v1/payments/{payment_id}/voidpayments.void

Void a payment

Cancels an unsettled transaction. Once settled, a void is refused and a refund is the correct operation — the state machine enforces this rather than letting the provider decide.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/payments/{payment_id}/void" \
  -H "Authorization: Bearer $MYELA_API_KEY"
POST/v1/payments/{payment_id}/refundpayments.refund

Refund a payment

Returns money on a captured transaction. Omit amount for a full refund. Partial refunds may be repeated up to the captured total.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/payments/{payment_id}/refund" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "amount": 500
}'
GET/v1/payments/{payment_id}payments.retrieve

Retrieve a payment

The authoritative state. status reflects settlement as reported by the provider, not an optimistic local guess.

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/payments/{payment_id}" \
  -H "Authorization: Bearer $MYELA_API_KEY"
GET/v1/paymentspayments.list

List payments

Cursor-paginated — see the collection description. startingAfter takes the id of the last row you saw.

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/payments?limit=10" \
  -H "Authorization: Bearer $MYELA_API_KEY"

Customers

A customer is the thing cards and subscriptions attach to. Creating one reserves it with the provider as well, so the same customer works for one-off charges and for recurring billing.

POST/v1/customerscustomers.create

Create customer

Only email is required. name falls back to the local part of the email when omitted.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/customers" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "{{$guid}}@example.com",
  "name": "Ada Lovelace",
  "phone": "+15555550123"
}'
GET/v1/customers/{customer_id}customers.retrieve

Retrieve customer

A customer id from another merchant is not_found, never permission_denied — confirming existence would leak it.

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/customers/{customer_id}" \
  -H "Authorization: Bearer $MYELA_API_KEY"
POST/v1/customers/{customer_id}customers.update

Update customer

POST, not PATCH — the update convention across this API. Only the fields you send change.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/customers/{customer_id}" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Ada King"
}'
GET/v1/customerscustomers.list

List customers

Cursor-paginated.

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/customers?limit=10" \
  -H "Authorization: Bearer $MYELA_API_KEY"

Payment methods

Cards on file. The card itself is captured in the browser and never reaches your server — you exchange a single-use token for a reusable payment method here.

POST/v1/payment_methodspaymentMethods.create

Create payment method (vault a card)

Consumes the single-use token and returns a reusable id. A card stored through /v1 with no stated schedule is credential-on-file unscheduled — the merchant charges it when they charge it.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/payment_methods" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "customerId": "{{CUSTOMER_ID}}",
  "paymentToken": "{{PAYMENT_TOKEN}}"
}'
GET/v1/payment_methodspaymentMethods.list

List payment methods

customerId is required — cards are always listed for one customer. The default card sorts first.

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/payment_methods?customerId={{CUSTOMER_ID}}&limit=10" \
  -H "Authorization: Bearer $MYELA_API_KEY"
POST/v1/payment_methods/{payment_method_id}/defaultpaymentMethods.setDefault

Set default payment method

Which card is charged when a request names none — including a scheduled invoice collecting itself.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/payment_methods/{payment_method_id}/default" \
  -H "Authorization: Bearer $MYELA_API_KEY"

Addresses

Billing and shipping addresses, held by Myela and mirrored to the provider where it can express them — so an address survives a change of provider.

POST/v1/addressesaddresses.create

Create address

Fields are allow-listed: anything else you send is not stored.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/addresses" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "customerId": "{{CUSTOMER_ID}}",
  "firstName": "Ada",
  "lastName": "Lovelace",
  "line1": "12 Marylebone Road",
  "city": "London",
  "postalCode": "NW1 5JD",
  "country": "GB"
}'
GET/v1/addressesaddresses.list

List addresses

Scoped to one customer.

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/addresses?customerId={{CUSTOMER_ID}}" \
  -H "Authorization: Bearer $MYELA_API_KEY"
GET/v1/addresses/{address_id}addresses.retrieve

Retrieve address

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/addresses/{address_id}" \
  -H "Authorization: Bearer $MYELA_API_KEY"
POST/v1/addresses/{address_id}addresses.update

Update address

Only the fields you send change.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/addresses/{address_id}" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "city": "Manchester"
}'
POST/v1/addresses/{address_id}/defaultaddresses.setDefault

Set default address

type is required and is either billing or shipping — a customer has one default of each, so a request that does not say which is refused rather than guessed.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/addresses/{address_id}/default" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "type": "billing"
}'

Plans and subscriptions

A plan is the price and cadence; a subscription binds a customer, a plan and a card. Amounts are integers in the minor unit — 4999 is $49.99. A decimal is rejected rather than silently charged as a different figure.

POST/v1/plansplans.create

Create plan

interval is one of day, week, month, year. An unknown interval is refused here rather than upstream.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/plans" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "name": "Standard monthly",
  "amount": 4999,
  "currency": "USD",
  "interval": "month",
  "intervalCount": 1
}'
GET/v1/plans/{plan_id}plans.retrieve

Retrieve plan

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/plans/{plan_id}" \
  -H "Authorization: Bearer $MYELA_API_KEY"
GET/v1/plansplans.list

List plans

Cursor-paginated.

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/plans?limit=10" \
  -H "Authorization: Bearer $MYELA_API_KEY"
POST/v1/subscriptionssubscriptions.create

Create subscription

Which calendar day a charge lands on is a timezone question, so timezone is explicit and defaults to UTC rather than being guessed.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/subscriptions" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "customerId": "{{CUSTOMER_ID}}",
  "planId": "{{PLAN_ID}}",
  "paymentMethodId": "{{PAYMENT_METHOD_ID}}",
  "startAt": "2026-09-01",
  "timezone": "America/New_York"
}'
GET/v1/subscriptions/{subscription_id}subscriptions.retrieve

Retrieve subscription

nextBillingAt is absent once cancelled — a stale date there is the shape of every "we cancelled but it billed again".

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/subscriptions/{subscription_id}" \
  -H "Authorization: Bearer $MYELA_API_KEY"
GET/v1/subscriptionssubscriptions.list

List subscriptions

Cursor-paginated.

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/subscriptions?limit=10" \
  -H "Authorization: Bearer $MYELA_API_KEY"
POST/v1/subscriptions/{subscription_id}/cancelsubscriptions.cancel

Cancel subscription

Cancels immediately.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/subscriptions/{subscription_id}/cancel" \
  -H "Authorization: Bearer $MYELA_API_KEY"

Invoices and scheduled collection

An invoice can be sent for someone to pay, or scheduled to collect itself from a saved card on a date. The schedule is Myela's own: no provider behind this API has a collect-on-a-date primitive.

POST/v1/invoicesinvoices.create

Create invoice

The invoice total, in minor units — there is no line-item array on this endpoint. Amounts are integers in the minor unit — 4999 is $49.99. A decimal is rejected rather than silently charged as a different figure.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/invoices" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "customerId": "{{CUSTOMER_ID}}",
  "currency": "USD",
  "amount": 150000,
  "description": "Consulting, August",
  "dueAt": "2026-09-01T00:00:00.000Z"
}'
GET/v1/invoices/{invoice_id}invoices.retrieve

Retrieve invoice

url is a Myela-hosted payment page on our own domain, safe to send to a cardholder.

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/invoices/{invoice_id}" \
  -H "Authorization: Bearer $MYELA_API_KEY"
GET/v1/invoicesinvoices.list

List invoices

Cursor-paginated.

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/invoices?limit=10" \
  -H "Authorization: Bearer $MYELA_API_KEY"
POST/v1/invoices/{invoice_id}/scheduleinvoices.schedule

Schedule invoice collection

Charges the named card on that date. Omit paymentMethodId to use the customer's default. Failed attempts retry on a backoff and are visible under Attempts.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/invoices/{invoice_id}/schedule" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "collectAt": "2026-09-01T09:00:00.000Z",
  "paymentMethodId": "{{PAYMENT_METHOD_ID}}"
}'
POST/v1/invoices/{invoice_id}/cancel_scheduleinvoices.schedule

Cancel scheduled collection

Leaves the invoice open but stops it collecting itself.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/invoices/{invoice_id}/cancel_schedule" \
  -H "Authorization: Bearer $MYELA_API_KEY"
GET/v1/invoices/{invoice_id}/attemptsinvoices.attempts

List collection attempts

Every attempt made to collect it, in order — the answer to "why has this not been paid".

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/invoices/{invoice_id}/attempts" \
  -H "Authorization: Bearer $MYELA_API_KEY"
POST/v1/invoices/{invoice_id}/voidinvoices.void

Void invoice

Terminal. A voided invoice cannot be collected or reopened.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/invoices/{invoice_id}/void" \
  -H "Authorization: Bearer $MYELA_API_KEY"

Webhook endpoints and deliveries

Where your payment events leave Myela. Entitlements here are separate from the rest of /v1 on purpose: a key that may read invoices should not be able to point that stream somewhere new. Verify every delivery signature before acting on it — see docs/gateway/api/WEBHOOK_SIGNATURE.md.

POST/v1/webhook_endpointswebhookEndpoints.create

Create webhook endpoint

The signing secret is returned once, at creation, and never again.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/webhook_endpoints" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/webhooks/myela",
  "events": [
    "invoice.paid",
    "payment.settled",
    "payment.refunded"
  ]
}'
GET/v1/webhook_endpointswebhookEndpoints.list

List webhook endpoints

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/webhook_endpoints" \
  -H "Authorization: Bearer $MYELA_API_KEY"
GET/v1/webhook_endpoints/{webhook_endpoint_id}webhookEndpoints.retrieve

Retrieve webhook endpoint

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/webhook_endpoints/{webhook_endpoint_id}" \
  -H "Authorization: Bearer $MYELA_API_KEY"
POST/v1/webhook_endpoints/{webhook_endpoint_id}webhookEndpoints.update

Update webhook endpoint

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/webhook_endpoints/{webhook_endpoint_id}" \
  -H "Authorization: Bearer $MYELA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "events": [
    "invoice.paid"
  ]
}'
DELETE/v1/webhook_endpoints/{webhook_endpoint_id}webhookEndpoints.delete

Delete webhook endpoint

Request
curl -X DELETE "https://payments-api-sandbox.merchantservicedepot.com/v1/webhook_endpoints/{webhook_endpoint_id}" \
  -H "Authorization: Bearer $MYELA_API_KEY"
GET/v1/webhook_deliverieswebhookDeliveries.list

List deliveries

Every attempt to reach your endpoint, with the response we got.

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/webhook_deliveries?limit=10" \
  -H "Authorization: Bearer $MYELA_API_KEY"
GET/v1/webhook_deliveries/{webhook_delivery_id}webhookDeliveries.retrieve

Retrieve delivery

Request
curl -X GET "https://payments-api-sandbox.merchantservicedepot.com/v1/webhook_deliveries/{webhook_delivery_id}" \
  -H "Authorization: Bearer $MYELA_API_KEY"
POST/v1/webhook_deliveries/{webhook_delivery_id}/replaywebhookDeliveries.retrieve

Replay delivery

Re-sends the FROZEN payload, not a rebuilt one — a replay must deliver what the event said when it happened.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/webhook_deliveries/{webhook_delivery_id}/replay" \
  -H "Authorization: Bearer $MYELA_API_KEY"

Myela Elements (browser)

The only two routes a merchant's own checkout page calls, and the only two authenticated by the PUBLISHABLE key (myela_pk_) rather than the secret one. Set MYELA_PUBLISHABLE_KEY in the environment; the secret key must never reach a browser. A session can only be started from an origin on that key's allow-list, which the merchant manages. An empty list refuses every origin — "not configured yet" fails closed, because a checkout that 401s until someone adds a domain is recoverable and one open to the internet is not. The hosted frame at js.myela.com is not serving yet, so the middle step — mounting the fields and getting an upstream token — cannot be done from these two requests alone. The server contract below is live and can be exercised today.

POST/v1/checkout_sessions

Create checkout session

Opens a checkout session from the merchant's page. intent is one_time (charge now) or store (vault the card); the whole body is optional and defaults to one_time. Returns sessionId, a mountUrl to iframe, parentOrigin, and an opaque capture object. Nothing in the response names a provider or differs by which one settles the account — that uniformity is the contract, not an implementation detail. 401 means the calling origin is not on this key's allow-list.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/checkout_sessions" \
  -H "Authorization: Bearer $MYELA_PUBLISHABLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "intent": "one_time"
}'
POST/v1/tokens

Exchange for a payment token

Exchanges the provider token the fields produced for an opaque mtok_, which is what POST /v1/payments accepts as paymentToken. The session id is the capability — there is no key on this call beyond the publishable one. A session exchanges ONCE; a second attempt is refused, and so is a replay of the resulting mtok_. Both variables come from the frame, so this request cannot be driven from the collection alone until js.myela.com is serving.

Request
curl -X POST "https://payments-api-sandbox.merchantservicedepot.com/v1/tokens" \
  -H "Authorization: Bearer $MYELA_PUBLISHABLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "sessionId": "{{CHECKOUT_SESSION_ID}}",
  "upstreamToken": "{{UPSTREAM_TOKEN}}"
}'