Skip to content
Coritan Docs

Build the customer account area

Let signed-in customers see their services, pay invoices, add credit, save payment methods and edit their profile.

View as Markdown

The account area is the part of your storefront a customer sees once signed in: their services, invoices, credit, saved payment methods, billing details and profile. Every call on this page goes to /portal/... with the customer's token as Authorization: Bearer $CUSTOMER_TOKEN, and answers with that customer's own records only.

  • Get a customer token as Sign customers in to your storefront describes.
  • Serve an invoice page at /billing/invoices/{invoice_id} on your Custom domain. Invoice emails link to it, and hosted checkouts return to it.
  • Payments go through Coritan's payment gateways for the customer's currency and country, or through your own PayNow store when your billing mode says so. See PayNow.
Shell
curl "https://api.coritan.com/api/v1/orgs/acme/portal/services?per_page=20" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN"

The list is newest first. status_filter takes one of the service statuses, page and per_page (1–200, default 50) page through it, and consistency=eventual suits a poll that runs while the page is open.

Each service carries id, status, hostname, product_name, plan_name, billing_cycle, amount, next_due_date and termination_date. termination_date is when the service is set to end, in UTC with its offset, such as 2026-10-13T12:00:00+00:00, and null when it is not. A cancellation for the end of the term writes it, and taking the cancellation back clears it. ends_at_term_end is true when the end date is such a cancellation, which the customer can take back, and false when there is no end date or our team set it. awaiting_payment and open_invoice_id point at an unpaid invoice for it, and pending_plan_change at an upgrade waiting for payment, or at a switch of billing period waiting for the next renewal. A provisioned service has a resource with its type, uuid, label, status, power and address. is_owner is false for a server another customer shared with this one. GET /portal/services/{service_id} returns one service in the same shape.

GET /portal/services/live?ids=41,42 says what the customer's servers are doing now: items with each service_id, kind and state, a checked_at time, and degraded when a server did not answer. We keep each answer for 10 seconds. Add refresh=true to ask again: requests for the same customer that arrive within a couple of seconds share one check.

For a game server behind a join address, PATCH /portal/services/{service_id}/gameproxy changes what players see in their server list: online_motd and offline_motd (up to 256 characters each), favicon, version_name, display_name, description, and status_cache_ttl_seconds (1–10). clear_favicon, clear_online_motd and clear_offline_motd remove a value.

  1. List the plans a service can move to with GET /portal/services/{service_id}/plans. They are sizes of the same product, for the same game and billing cycle, and current marks the plan it is on. Each has its pricing id, name, price, currency, ram_mb, vcpu and disk_mb.
  2. Show the cost with GET /portal/services/{service_id}/plan-preview?org_pricing_id=88. The answer says whether the move is an upgrade, a downgrade or lateral in change_type, what is due_today, and for a downgrade whether credit will be issued (credit_will_be_issued, credit_estimate, and credit_declined_because when it will not).
  3. Make the change with an Idempotency-Key, so a retry does not ask twice:
Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/services/41/change-plan" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN" \
  -H "Idempotency-Key: 5c0e2a4e-plan-88" \
  -H "Content-Type: application/json" \
  -d '{"org_pricing_id": 88}'

An upgrade answers requires_payment: true with an invoice_id. The invoice charges the difference for the days left in the cycle, or a full cycle at the new price when no days are left, and the new plan applies once it is paid. Asking for a different upgrade before paying cancels the first invoice. POST /portal/services/{service_id}/cancel-plan-change cancels an unpaid upgrade.

An upgrade can take a coupon code of your store. Add coupon_code to plan-preview (as a query parameter) and to the change-plan body. The quote's coupon is {code, discount} and due_today is what is left to pay, and the upgrade invoice carries a discount line for the code. A code that does not apply answers 422 with errors, the sentence to show, and coupon, the reason (unknown, inactive, not_yet, expired, wrong_product, already_used, exhausted, disabled or nothing_off), as at checkout. The use is counted when the upgrade invoice is paid, so an upgrade left unpaid uses nothing up. A code that takes the whole charge applies the upgrade at once, and change-plan answers requires_payment: false.

A free server may move onto a paid plan of any billing period you sell, such as a weekly one, because it has no paid period to keep. When the period is not the free plan's own, plan-preview answers first_period: true with due_today set to the plan's price for one whole period, and days_remaining set to the period's length. The invoice charges that one period, and the period starts on the day the invoice is paid. Onto the free plan's own period, the upgrade charges the days left in the free month as for any other server, and first_period is false.

A downgrade or a move at the same price applies at once and answers "message": "Plan updated". Coritan sets whether your organization credits the unused days of a downgrade, and every credit keeps back at least one day.

Switch a rank between monthly and annual billing

Section titled Switch a rank between monthly and annual billing

A service on a Standalone product, such as a rank, has no sizes. Its product has one plan for each billing period, such as a monthly plan and an annual plan, and the customer can move between them. The switch costs nothing today and waits for the next renewal, because the period already paid for is what the customer bought.

  1. List the plans with GET /portal/services/{service_id}/plans. The list holds the product's active plans in the currency the service is billed in, shortest period first, and current marks the plan the service is on. A plan with applies_at of next_renewal is another billing period. scheduled is true on the plan a switch is waiting for. A plan that is switched off, a one-time price, and a plan in another currency are not listed.
  2. Show what happens with GET /portal/services/{service_id}/plan-preview?org_pricing_id=2. The answer has change_type period, due_today 0, applies_at next_renewal, current_amount and new_amount, current_billing_cycle and billing_cycle, and effective_at, the date of the first renewal at the new period. renewal_invoice_open is true when the next renewal is already invoiced, and then effective_at is the renewal after it.
  3. Ask for the switch with POST /portal/services/{service_id}/change-plan, sending the same body and Idempotency-Key as for any plan change:
JSON
{
  "message": "Your plan will change at your next renewal",
  "service_id": 41,
  "org_pricing_id": 1,
  "amount": 5.0,
  "invoice_id": null,
  "requires_payment": false,
  "pending": true,
  "scheduled": true,
  "applies_at": "next_renewal",
  "effective_at": "2026-10-13T12:00:00",
  "new_org_pricing_id": 2,
  "new_amount": 50.0,
  "new_billing_cycle": "annually",
  "plan_name": "Annual",
  "currency": "USD",
  "replayed": false
}

Until then the service keeps its plan, its amount and its due date, and the service's pending_plan_change holds the switch. Asking for the same plan again answers the same thing with replayed: true. Asking for another billing period replaces the first. POST /portal/services/{service_id}/cancel-plan-change calls it off and answers cancelled: true with invoice_id null and applies_at next_renewal.

When we invoice the renewal, the service moves onto the new plan first. The invoice bills the new plan's name and the price the customer was shown, the service's amount, billing_cycle and plan_name change, and paying the invoice moves next_due_date on by the new period. If the plan was switched off or changed currency in the meantime, the switch lapses and the service renews on the plan it is on.

A plan of another billing period is refused for a paid server and for any other product that Coritan runs for you, with Plan changes must keep the current billing cycle. A free server is the exception, as Change a plan describes. It is also refused with 409 while the service is set to end, and while an upgrade is waiting for payment. Cancelling the service clears a switch that is waiting.

  1. Show what the customer gets back with GET /portal/services/{service_id}/cancel-preview?immediate=true. The answer carries will_credit and credit_amount, declined_because when there is no credit, and falls_back_to_free with the free_plan_name when the server moves to your free plan instead of ending. For a server it also lists the server's addons, so the customer can choose which to keep.
  2. Cancel:
Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/services/41/cancel" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"immediate": false, "reason": "Moving to a bigger plan elsewhere"}'
Field Meaning
immediate false (the default) keeps the service until its next due date. true ends it now.
reason Why the customer is leaving. It goes in your audit log.
keep_snapshot Servers only. true (the default) saves a snapshot before the server goes.
keep_ipv4 Cloud Compute only. true keeps the public IPv4 address on the account at its own price.
delete_server true deletes the server even when your organization sells a free plan. It needs immediate: true.
keep_addon_ids Servers only. The org_service_id of each add-on to keep when the server moves to your free plan. The others end with the server.

We email the customer. An open invoice that bills only this service is cancelled. An open invoice that also bills other services, such as a basket, stays open for them: we take this service's lines off it, keep the coupon's share of what is left and work out the tax again. When nothing is left to pay, we settle it and start the services still on it. A shared invoice that has had a payment is cancelled whole instead. An invoice that a payment gateway took part of stays open, because a cancelled invoice cannot keep that money, and the answer lists it in invoices_left_open.

A server's add-ons are services of their own, so the customer cancels one the same way. Cancelling the server ends its add-ons with it, at once or at the end of the term. When the server moves to your free plan, the add-ons named in keep_addon_ids stay on it with billing of their own, and the rest end.

When your organization sells a free plan, a cancelled container moves to it and keeps its files, at once or at the end of the term. Coritan sets whether an immediate cancellation credits the unused days; an end-of-term cancellation uses every day it paid for. The answer has the status, the termination_date, the credit_issued, snapshot_taken, moved_to_free, and invoices_reduced, which lists each shorter invoice with its new total.

A service cancelled for the end of the term runs until its termination_date. Until that date, the customer can keep it:

Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/services/41/keep" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'

The answer is the service, as GET /portal/services/41 returns it, with termination_date set to null. A server that would have moved to your free plan on that date stays on its paid plan. Your webhooks receive service.cancellation_withdrawn (Service events).

The cancellation had cancelled any unpaid invoice for the service, and keeping the service does not reopen it. The next renewal is invoiced as usual, on the plan the service is on now, and a plan change the cancellation cleared is not brought back. A customer can keep the same service up to 20 times an hour. A support session can keep a service only where it can cancel one.

The call answers 409 with an error and a message when there is nothing to keep:

error When
not_cancelled The service is not set to end.
already_ended The service has ended or is being removed, or its termination_date has passed.
not_active The service is suspended or not running. Contact support.
scheduled_by_support Your team set the end date, as part of an abuse suspension. The customer cannot withdraw it.

Another customer's service, and another organization's, answers 404.

GET /portal/invoices lists the customer's invoices, newest first. status_filter takes one of the invoice statuses, or open for unpaid and overdue together. q searches invoice numbers. limit and offset page through the list, and with_total=true wraps it as items, total and counts for each status.

Each invoice has coupon, the code of the coupon it used (or null), and discount, the amount its discount lines take off (0 with none), in the invoice's currency. Each line has product_slug and product_name, the product its service runs, or null for a line with no service. The analytics and ad tags use them as the item ID and name, and the server-side conversions send the same ones.

GET /portal/invoices/{invoice_id} adds the payments taken against the invoice, payment_processing while a slower payment method settles, and for an open invoice retry: when we will next try the saved payment method (next_retry_at), and how many attempts of max_attempts have run. GET /portal/invoices/{invoice_id}/pdf downloads the invoice as a PDF, and GET /portal/invoices/{invoice_id}/delivery shows how far each store item and server on it has got (see Follow the delivery of store items and servers).

Follow the delivery of store items and servers

Section titled Follow the delivery of store items and servers

After a customer pays for Standalone products, such as the items in a basket, your game servers deliver them and record how each delivery went. A server in the basket is set up by Coritan. Show the customer how far each one has got:

Shell
curl "https://api.coritan.com/api/v1/orgs/acme/portal/invoices/5022/delivery" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN"
JSON
{
  "invoice_id": 5022,
  "status": "paid",
  "items": [
    {
      "service_id": 7101,
      "kind": "item",
      "name": "VIP rank",
      "plan": null,
      "quantity": 1,
      "player": {"name": "Steve", "edition": "java"},
      "gift_from": null,
      "address": null,
      "server_uuid": null,
      "status": "delivered",
      "delivered_at": "2026-10-17T14:03:00Z",
      "detail": "Your rank is active on every server."
    },
    {
      "service_id": 7102,
      "kind": "server",
      "name": "Survival SMP",
      "plan": "Standard 4 GB",
      "quantity": 1,
      "player": null,
      "gift_from": null,
      "address": "survival-smp.example.gg",
      "server_uuid": "6f1c0a52-3c1d-4f0e-9a52-0d7d6c1c6f11",
      "status": "setting_up",
      "delivered_at": null,
      "detail": null
    }
  ]
}
  • items lists the invoice's store items and servers in the order of the invoice, one entry for each bought, so three packs are three entries with a quantity of 1. Other charges on the same invoice, and a platform product that is not a server on a container plan, are left out. kind is item for a store item and server for a server.
  • A store item's status is pending_payment until the invoice is paid, then waiting until a game server records it, then delivered or failed. detail is the game server's note, and delivered_at says when it delivered the item.
  • player is whom a store item was bought for, from the order form's minecraft_name and minecraft_edition, or null. gift_from is the sender's name the buyer gave for a gift.
  • A server's status is pending_payment until the invoice is paid, then setting_up until the server exists and has finished installing, then ready. It is failed, with a sentence in detail, when we could not set it up or the server has been deleted since. name is the name the customer gave the server and plan the plan it runs on.
  • A server's address is where players join. Once the server has an address of its own, it is that one. Until then it is the name the customer chose under your game proxy domain, so the page can show where the server will be, and it is null when there is no domain to build it from. server_uuid is the ID the /portal/servers/{uuid} calls take, and it is null until the server exists. A failed server keeps its server_uuid when it was created, so the customer can still open it.

Poll the call every few seconds until no entry is pending_payment or setting_up.

Your program records each store item's delivery as Record a delivery describes. An invoice that is not this customer's answers 404 with Invoice not found.

Choose the way that suits your page. Each works on an unpaid or overdue invoice.

Hosted checkout
POST /portal/invoices/{invoice_id}/checkout answers a checkout_url to send the customer to. It follows the same rules as POST /storefront/checkout/{invoice_id}; see Pay an invoice. Leave out gateway_name and we choose a gateway that takes the pay currency: currency when you send it, otherwise the customer's currency. POST /portal/invoices/{invoice_id}/pay and GET /portal/invoices/{invoice_id}/gateways are deprecated and may stop answering after 31 March 2027, so call checkout in their place.
A saved payment method
POST /portal/invoices/{invoice_id}/charge?payment_method_id=7 charges a saved method, or the default one without payment_method_id. When the bank asks the customer to confirm, the answer is requires_action with a client_secret; after the customer confirms, call POST /portal/invoices/{invoice_id}/confirm-payment?gateway_name=...&payment_intent_id=.... Send currency, the code your page showed the customer, to charge in it. Without it we use the method's own currency, then the customer's. A currency that customers cannot pay in answers 400 with Payment currency SEK is not enabled. When the payment account that holds the card does not take the currency you send, we answer 400 with This card cannot pay in EUR. Choose another card or pay with a new card. and write and charge nothing. A Square card takes US dollars only. A saved bank method pays in one currency: a SEPA Direct Debit method pays in euros, a US bank account in dollars and a Bacs method in pounds. For any other currency we answer 400 with This payment method cannot pay in USD. Choose another method. and write and charge nothing. A method whose bank ended the mandate answers 400 with Your bank ended the permission to charge this account. Choose another payment method., and we stop charging it. A bank debit answers pending and settles days later.
A card form in your page
GET /portal/payment-config returns each gateway's public_config, such as the publishable key your card form needs. POST /portal/invoices/{invoice_id}/pay-intent returns a client_secret for the form, with save_method=true to keep the method for renewals. An invoice that renews a service saves each method that can be saved, and the methods that cannot be saved (Klarna, Przelewy24, TWINT and the like) stay on the form and are paid once. The answer's status is requires_payment_method, succeeded or pending. pending means a payment of this invoice is processing at the bank (SEPA, ACH and Bacs take days): it has no client_secret and holds a message to show, and we make no second payment, so the customer cannot pay twice. Once the customer has paid, call confirm-payment as above. payment-config lists Stripe accounts unless you send card_adapters=stripe,square, which says your page can also draw Square's card form.
Apple Pay and Google Pay buttons
Ask for the amount with GET /portal/invoices/{invoice_id}/pay-quote before you show the buttons, then send it to pay-intent as expected_amount. See Show Apple Pay and Google Pay buttons.
A Square card form
We no longer take payments with Square, so a quote never names a Square account and POST /portal/invoices/{invoice_id}/square-pay charges nothing. Draw the Stripe form. Take a card payment with Square describes the route while it remains.
PayPal buttons
Save the approved order with POST /portal/invoices/{invoice_id}/paypal-stash?order_id=...&gateway_name=..., then take the money with POST /portal/invoices/{invoice_id}/paypal-capture and the same parameters.
Account credit
POST /portal/invoices/{invoice_id}/apply-credit pays what it can from the customer's balance and answers amount_applied, fully_paid and amount_remaining.

GET /portal/invoices/{invoice_id}/checkout-session resumes a checkout the customer left, for example after opening a new tab. It answers open, and for an open checkout the gateway_name, client_secret or paypal_order_id, and when it expires_at.

Show Apple Pay and Google Pay buttons

Section titled Show Apple Pay and Google Pay buttons

Apple Pay and Google Pay buttons let a customer pay with a card kept in their phone or browser, through Stripe's Express Checkout Element. The sheet the customer approves already shows the amount, so your page asks us for a quote before it shows the buttons, and creates the payment only once the customer approves. The same quote also tells a page which card form to draw: see Take a card payment with Square.

  1. Ask for a quote before the customer selects a button, because Stripe gives the button's click handler one second to answer. Ask again whenever the customer changes the amount or the currency.
Shell
curl "https://api.coritan.com/api/v1/orgs/acme/portal/invoices/5021/pay-quote?currency=EUR" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN"

For credit, ask GET /portal/credit/topup/pay-quote?amount=25. An invoice quote takes currency, country, gateway_name, save_method and card_adapters, and a credit quote takes amount, currency, gateway_name, save_method and card_adapters, each as its pay-intent takes them. card_adapters is a comma list of the card forms your page can draw, stripe and square. It defaults to stripe, so a page with no Square card form always gets a Stripe account, whatever your routing rules prefer. A payment that must keep the card for renewals (an invoice that renews a service, or a quote with save_method true) also gets a Stripe account while Square cannot keep cards. A quote creates nothing and does not contact the card processor. The answer for a Stripe account:

JSON
{
  "gateway_name": "stripe",
  "adapter": "stripe",
  "public": {"adapter": "stripe", "account": "stripe", "publishable_key": "pk_live_...", "payment_method_configuration": "pmc_..."},
  "publishable_key": "pk_live_...",
  "payment_method_configuration": "pmc_...",
  "currency": "EUR",
  "charge_amount": 990,
  "base_amount": "11.0000",
  "fx_rate": "0.90000000",
  "save_method": true,
  "business_name": "Acme Hosting Ltd",
  "label": "Invoice INV-1042",
  "renewal": {
    "amount": 990,
    "label": "Monthly renewal",
    "interval_unit": "month",
    "interval_count": 1
  }
}
  • gateway_name is the account we charge through, and adapter says which card form to draw, stripe or square. public holds that account's settings that are safe to show in a browser. publishable_key and payment_method_configuration are a Stripe account's, and the configuration can be null.
  • charge_amount is what the customer pays, in the smallest unit of currency, such as cents. base_amount is the US dollars it pays, and fx_rate the rate we converted at.
  • save_method says whether we keep the card for renewals. When you leave it out, it is true for an invoice that renews a service and false for credit.
  • save_types lists the payment method types the payment keeps for later charges, such as card, sepa_debit or us_bank_account. It is empty when save_method is false. local_methods lists the methods made for the customer's country and currency that this account can show, such as ideal or blik, and is empty when there are none.
  • business_name is the company_name in your organization settings, or your organization's name when that is empty. label is Invoice and the invoice number, or Account credit.
  • renewal is what the next renewal charges, when we keep the card and every service on the invoice renews on the same billing cycle. Otherwise it is null, and it is always null for credit.
  1. Load Stripe.js with publishable_key. Create the elements with mode: "payment", amount: charge_amount, currency in lower case, paymentMethodConfiguration: payment_method_configuration, and, when save_method is true, a paymentMethodOptions object that sets setup_future_usage to "off_session" for each type in save_types. Leave out a top-level setupFutureUsage: Stripe then hides every method that cannot be kept for later, such as iDEAL or Klarna. Mount an expressCheckout element with business: {name: business_name}.
  2. In the element's click handler, resolve with one line item: label for charge_amount. With Apple Pay and a renewal, also send applePay.recurringPaymentRequest: renewal.label as its paymentDescription and regularBilling.label, renewal.amount as regularBilling.amount, interval_unit and interval_count as recurringPaymentIntervalUnit and recurringPaymentIntervalCount, and your payment methods page as managementURL.
  3. In the confirm handler, call elements.submit(). Then create the payment with the quote's gateway_name, currency and save_method, the country or amount you asked about, and charge_amount as expected_amount:
Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/invoices/5021/pay-intent?currency=EUR&gateway_name=stripe&save_method=true&expected_amount=990" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN"

For credit, call POST /portal/credit/topup/pay-intent?amount=25 with the same parameters. Pass the answer's client_secret to stripe.confirmPayment, then call confirm-payment as for a card form. Both confirm-payment calls write nothing and refuse a payment this customer did not make for that invoice or to add credit: 400 for another invoice, another organization or the other purpose, 403 for another customer's payment, and 409 for a payment that already settled something else or that does not name its invoice or top-up yet. The payment then settles by itself once the card processor confirms it.

When the amount changed after the quote, for example because the exchange rate moved, pay-intent answers 412 and creates nothing. detail.code is amount_changed, detail.message is a sentence you can show the customer, and detail.quote is a new quote in the shape above. Call event.paymentFailed({reason: "fail"}), update the elements to the new charge_amount and currency, and let the customer pay again.

Stripe shows a button wherever the browser and device can pay with that wallet, and only on a domain registered with the Stripe account. The element is created with applePay and googlePay set to always, so a customer on a computer sees Apple Pay as a QR code to scan with an iPhone on iOS 18 or later, and Google Pay in browsers where auto would hide it, even before the browser can see a card. Set link, amazonPay and klarna to auto, so each shows where the customer can use it, and paypal to never: PayPal is a payment method of its own on the pay step. If your storefront runs on a domain of its own, ask support to register it for Apple Pay and Google Pay.

Take a card payment with Square

Section titled Take a card payment with Square

We no longer take payments with Square. No Square account is open, so a quote never names one, and square-pay refuses every payment and charges nothing. Draw the Stripe form. The rest of this section describes the route while it remains.

Square is a second card account beside Stripe. It takes US dollars only. Saved Square cards are off until we switch them on for the Square account, so while they are off a Square payment does not keep the card and never renews by itself (Save a Square card). Square has no intent to create first. Your page tokenizes the card with Square's Web Payments SDK and sends the token, and we charge it and settle the invoice in the same request. We charge through the Square location of your store's brand, or our default location when your brand has none.

  1. Ask for a quote with card_adapters=stripe,square. Without card_adapters we never pick Square.
Shell
curl "https://api.coritan.com/api/v1/orgs/acme/portal/invoices/5021/pay-quote?card_adapters=stripe,square" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN"

When adapter is stripe, follow Show Apple Pay and Google Pay buttons. When it is square, the answer looks like this:

JSON
{
  "gateway_name": "square-coritan-americas",
  "adapter": "square",
  "public": {
    "adapter": "square",
    "account": "square-coritan-americas",
    "application_id": "sq0idp-...",
    "location_id": "L8F2...",
    "sandbox": false,
    "can_save_cards": false,
    "google_pay": false,
    "apple_pay": false,
    "ach": false
  },
  "currency": "USD",
  "charge_amount": 1100,
  "base_amount": "11.0000",
  "fx_rate": "1",
  "save_method": false,
  "business_name": "Acme Hosting Ltd",
  "label": "Invoice INV-1042",
  "renewal": null
}

charge_amount is in cents. It is the dollar amount of the invoice, converted when the invoice is in another currency. A customer who prefers another currency still pays Square in dollars, and a quote asked for another currency names a Stripe account. GET /portal/payment-config?card_adapters=stripe,square lists the same accounts, each with its public_config.

  1. Load Square's script from https://web.squarecdn.com/v1/square.js, or from https://sandbox.web.squarecdn.com/v1/square.js when public.sandbox is true. Load it when the customer opens the card step, not when the page loads. Create the payments object with public.application_id and public.location_id, attach a card form, and reserve the form's height so the page does not move.
  2. When the customer pays, call tokenize() with verificationDetails: intent CHARGE, amount from charge_amount in dollars, currencyCode USD, billingContact, and customerInitiated true. It answers a token and, for a card that needs it, a verificationToken.
  3. Send them to square-pay, with the quote's charge_amount as expected_amount:
Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/invoices/5021/square-pay" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"source_id": "cnon:...", "verification_token": "verf:...", "expected_amount": 1100}'
Field Meaning
source_id Required. The token from tokenize(). A token works once.
expected_amount The quote's charge_amount. When the amount due has changed, we answer 412 and charge nothing.
verification_token The verificationToken from tokenize(), when it gave one.
buyer_email Where Square sends its receipt. Leave it out for no Square receipt.
billing The billing contact, as tokenize() was given it.
gateway_name A Square account of ours. Leave it out and we use the account the quote named.
currency USD. Any other value answers 400.
save_method true to keep the card for renewals. We honour it only while saved Square cards are on, and ignore it otherwise.
amount_usd Ignored for an invoice, which is paid in full.

Every answer has status, attempt_id, gateway_reference, charge_amount and currency, and message when there is something to show. A succeeded answer for an invoice also has invoice_status and base_amount:

JSON
{
  "status": "succeeded",
  "attempt_id": null,
  "gateway_reference": "bPhQ...",
  "charge_amount": 1100,
  "currency": "USD",
  "invoice_status": "paid",
  "base_amount": "11.0000"
}

status is one of:

  • succeeded. Square took the payment and we booked it on the invoice.
  • failed. The card was declined and nothing was booked. The answer is still HTTP 200. Show message and let the customer try another card. The new token starts a new try.
  • pending. Square has not finished, or we could not read its answer. The payment may have gone through, so do not charge again. message says so. Read GET /portal/invoices/{invoice_id} until status is paid. While a payment is pending, GET /portal/invoices/{invoice_id}/checkout-session answers payment_processing true.

Sending the same token twice is one payment. While the first request for an invoice is still running, a second answers 409 with a Retry-After header, and so does a new card while an earlier payment is pending, so two cards sent together never both charge. Square charges whole cents, so a top-up amount is rounded to cents. 429 follows 20 tries in 10 minutes for a customer or 60 for one client address, and a currency other than USD answers 400. A pending answer, including one after Square took the money and our booking failed, is finished by Square's webhook: an organization's payment has no attempt row for a reconcile job to look up, so read the invoice or the balance until it changes. attempt_id is always null for a customer of an organization.

When you sent save_method true and Square took the payment, the answer also holds saved_method: true when we saved the card for this customer, and false with a plain message when we could not. The payment stays succeeded and the invoice stays paid either way. A wallet payment cannot be saved. The answer has no saved_method when you did not ask, or while saved cards are off.

For credit, send the same body to POST /portal/credit/topup/square-pay?amount=25, with the amount you asked the quote about. The answer has the same fields, and the credit is added once Square takes the payment.

Save a payment method with Stripe

Section titled Save a payment method with Stripe

Call POST /portal/payment-methods/setup with return_url, cancel_url and gateway_name. For a Stripe account the answer holds a client_secret for stripe.confirmSetup. The form offers the methods the account has switched on for saving, such as cards, Link, SEPA Direct Debit and US bank accounts. iDEAL and Bancontact are saved as a SEPA Direct Debit method. When the customer is back, call POST /portal/payment-methods/confirm?gateway_name=<account>&session_id=<session_id>. The answer always has a status.

  • succeeded. The method is saved, and the answer holds it. A method the customer already saves answers the method it already is.
  • processing. The bank is still confirming the method, as it does for a US bank account after micro-deposits, which can take days. Nothing is saved yet. Show message. We save the method when the bank confirms, and GET /portal/payment-methods then lists it.
  • requires_action. The customer has a step to finish. The answer holds message, the client_secret that finishes it in the page and, when the step is a page of the bank's, next_action_url.

A setup that failed answers 400 with We could not save this payment method. Start again and enter the details again. A cancelled one answers 400 with This setup was canceled. Start again to save a payment method.

Saved Square cards are off until we switch them on for the Square account. While they are off, POST /portal/payment-methods/setup and POST /portal/payment-methods/confirm with a Square gateway_name answer 400 with Saved cards are not switched on for this card account. and create nothing, and no Square account is offered for saving.

When they are on, a customer can save a card with or without a payment.

  • With a payment, send save_method true to square-pay, as above.
  • Without one, call POST /portal/payment-methods/setup with return_url, cancel_url and gateway_name (or card_adapters=stripe,square to let us pick). The answer has session_id square, adapter square, requires_redirect false, no client_secret and no redirect_url. public_config holds the application_id and the location_id of your store's brand for Square's card form. Then call tokenize() with intent STORE, and send the token to POST /portal/payment-methods/confirm?gateway_name=<account>&session_id=square&set_as_default=true with the JSON body {"source_id": "<token>", "verification_token": "...", "cardholder_name": "...", "billing": {...}}. Only source_id is required. A payment_id in the body is ignored: a card is saved from a token, or from a payment taken in the same request, and never from an id the customer names.

The answer is the saved method. A card the customer already saves answers the method it already is. A card of one organization is never charged for another, and a customer sees only their own cards. DELETE /portal/payment-methods/{method_id} also disables the card at Square.

We charge a saved Square card for renewals and for POST /portal/invoices/{invoice_id}/charge without asking the customer to confirm. We charge it in US dollars only, and we never charge it after the month it expires. A decline that a new card fixes, such as an expired card, stops us trying that card until the customer saves another or Square sends its new details. When the customer removes the Square card that was their default, their newest other Square card becomes the default.

Before we ask Square, we mark the invoice as being paid, with the reference_id we send, so Square's confirmation and our own look-up find the payment. When Square does not answer in time, POST /portal/invoices/{invoice_id}/charge answers 200 with status pending and a message that says not to pay again, and POST /portal/credit/topup/charge does the same. The charge is not a decline: we do not charge the invoice again until Square's confirmation arrives or our look-up finds no payment after 30 minutes. A second charge meanwhile answers pending with the same message.

If your page sets a Content Security Policy, allow https://web.squarecdn.com in script-src, frame-src and style-src, https://pci-connect.squareup.com and https://o160250.ingest.sentry.io in connect-src, and https://square-fonts-production-f.squarecdn.com, https://d1g145x70srn7h.cloudfront.net and https://cash-f.squarecdn.com in font-src. For the sandbox, use https://sandbox.web.squarecdn.com and https://pci-connect.squareupsandbox.com in place of the first two hosts.

A customer can ask for a paid invoice's money back, and your staff decide in the staff console.

  1. Call GET /portal/invoices/{invoice_id}/refund-eligibility before showing the form. eligible says whether the customer can ask; blocker_messages says why not, and reason_codes lists the reasons to offer.
  2. Send the request:
Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/invoices/5021/refund-request" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason_code": "downtime", "reason_text": "The server was offline for most of the weekend."}'

reason_code is not_delivered, duplicate_charge, downtime or other. reason_text takes up to 2,000 characters, and other needs at least 10. We email the customer that the request arrived.

An invoice takes one pending request at a time. Payments in cryptocurrency are never refunded, and neither are services ended for breaking the terms of service. A request within 24 hours of paying for a new service, or 48 hours for a renewal, is inside the refund window; a later one still reaches your staff, marked as outside it.

When your staff approve a request, the money goes back to the payment method or onto the customer's balance. A full refund ends every service the invoice paid for at once, each item of a basket included. A refund of part of an invoice ends nothing until the refunds on it add up to all of it. GET /portal/refund-requests lists the customer's requests with their status: pending, approved, rejected, withdrawn or failed. POST /portal/refund-requests/{request_id}/withdraw withdraws one that is still pending.

Coritan can switch the request form off for your organization, so that customers ask for a refund in a billing support ticket and your staff refund the invoice from its page. The eligibility answer's self_serve is then false, and POST /portal/invoices/{invoice_id}/refund-request answers 403 and creates nothing. Send the customer to a new ticket in your Billing department instead. The customer can still list their requests and withdraw a pending one.

GET /portal/credit returns the customer's balance, which we hold in US dollars, and its history, newest first. credit_currency names the currency of balance and of every history amount. It is always USD, whatever currency the customer pays in, and currency repeats it. Each entry has the amount, the balance_after, a description, its kind, and the invoice_id or org_service_id it belongs to. limit (up to 100) and offset page through the history.

A top-up takes an amount in US dollars from $5 to $10,000, charged in the customer's currency:

  • POST /portal/credit/topup/checkout?amount=25 answers a hosted checkout. Without return_url and cancel_url, the customer returns to /billing?paid=1 or /billing?cancelled=1 on your site.
  • POST /portal/credit/topup/charge?amount=25&payment_method_id=7 charges a saved method and answers the new_balance. Add currency to charge in it. The 400 for a card whose account does not take it is the one under Take a payment.
  • POST /portal/credit/topup/pay-intent?amount=25 returns a client_secret for a card form. After the customer pays, POST /portal/credit/topup/confirm-payment?gateway_name=...&payment_intent_id=... adds the credit.
  • GET /portal/credit/topup/pay-quote?amount=25 answers the amount and the card account for Apple Pay and Google Pay buttons. See Show Apple Pay and Google Pay buttons.
  • POST /portal/credit/topup/square-pay?amount=25 charges a Square card token and adds the credit. See Take a card payment with Square.
  • POST /portal/credit/topup/paypal-capture?order_id=...&gateway_name=... captures an approved PayPal order and adds the credit.

GET /portal/transactions lists every payment, refund, chargeback, credit added and credit spent. type_filter takes payment, refund, chargeback, credit_add or credit_deduct, and with_total=true adds the counts.

  1. GET /portal/crypto/assets lists what the customer can send: each network and asset, the min_deposit_usd, the confirmations a deposit waits for, and the applied_rate_usd.
  2. GET /portal/crypto/plan?amount=25 lists every way to pay that amount, cheapest first. Add invoice_id to plan for what an invoice still owes.
  3. GET /portal/crypto/quote?network=base&asset=USDC&amount=25&invoice_id=5021 says exactly how much to send. With invoice_id, the deposit pays that invoice when it arrives; without it, the deposit becomes credit.
  4. GET /portal/crypto/address?network=base returns the customer's deposit address. It stays the same, and one address serves every network of the same family.
  5. POST /portal/crypto/check with {"network": "base"} looks for the payment now. GET /portal/crypto/deposits lists every deposit, with the ones still confirming. GET /portal/crypto/deposits/{deposit_id} returns one deposit, but it is deprecated and may stop answering after 31 March 2027, so find the deposit in the list instead.
  1. Start with POST /portal/payment-methods/setup?return_url=...&cancel_url=.... The answer has a redirect_url to send the customer to, or a client_secret for a card form, and the session_id. Add country (a two letter code) to set the customer's billing country for this call, and gateway_name to name the account yourself.
  2. When the customer comes back, call POST /portal/payment-methods/confirm?gateway_name=...&session_id=.... The method becomes the default unless you send set_as_default=false.

GET /portal/payment-methods lists the saved methods with their display_label, brand, last4, expiry and is_default. PUT /portal/payment-methods/{method_id}/default makes one the default, and DELETE /portal/payment-methods/{method_id} removes it.

We charge the saved method for open invoices on our own while the customer's auto_pay_enabled is true, which it is unless they turn it off.

  • GET /portal/currencies lists the currencies a customer can pay in, US dollars first, each with its quote_currency, name, rate against the US dollar and decimal_places. A currency is listed only when it is payable: it has a rate and at least one enabled payment account takes it. Every code in the list works for PATCH /portal/payment-preference and for a payment.
  • GET /portal/countries lists the billing countries by region, with the one selected for this customer and the one detected from the connection.
  • GET /portal/geo-currency suggests a currency from the visitor's country, or US dollars when the country's currency is not payable. It needs no token, so your pages can use it before sign-in.
  • GET /portal/payment-preference returns the customer's preferred_payment_currency, credit_currency (always USD), country_code, billing_country, auto_pay_enabled, a suggested_currency, and currency_source and country_source, which say how each was set: default, geo, user or admin. PATCH on the same path changes them. A currency that is not payable answers 400 with Currency SEK is not enabled, and US dollars are always accepted:
Shell
curl -X PATCH "https://api.coritan.com/api/v1/orgs/acme/portal/payment-preference" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"currency": "EUR", "country_code": "DE", "auto_pay_enabled": true}'

Credit stays in US dollars. The preferred currency decides what we charge the customer in, converted when they pay.

A checkout can ask for the customer's billing details and keep them on the account for next time:

Shell
curl -X PATCH "https://api.coritan.com/api/v1/orgs/acme/portal/billing-details" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"first_name": "Alex", "last_name": "Stone", "country_code": "gb", "address": {"line1": "1 High Street", "city": "London", "postal_code": "SW1A 1AA"}}'

The answer is the details as saved, in the shape GET /portal/billing-details returns: email, first_name, last_name, company, country_code and address. address holds line1, line2, city, postal_code and state, or is null until the customer saves one.

  • Send only the fields to change. An empty or null first_name, last_name or company clears it. They are the same fields the profile holds.
  • Names take up to 100 characters and company up to 255. In address, line1, line2 and city take up to 200 characters, postal_code up to 20 and state up to 100.
  • country_code takes a two-letter code that GET /portal/countries lists, such as GB, in either case. It is the billing country PATCH /portal/payment-preference also sets.
  • address replaces the whole address, and null removes it.
  • email is accepted only when it is the account's own address, so a form can send back what GET answered. To change the email address, use the profile, as Keep the session going describes.
  • A support session can read the details but not change them, in any mode: PATCH answers 403.

Invoice PDFs show the name, the company, the address with the country's name under it, and the email. Without an address, they show the country's code after the email.

Edit the profile or close the account

Section titled Edit the profile or close the account

PATCH /portal/profile changes the profile exactly as PATCH /auth/me does; see Keep the session going.

To close the account, the customer confirms who they are again:

Shell
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/account/close" \
  -H "Authorization: Bearer $CUSTOMER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"password": "a long passphrase", "reason": "No longer needed"}'

A customer who signs in only with a social provider sends reauth_token instead of password. Get one from GET /auth/oauth/{provider}/authorize?intent=reauth with a provider already connected to the account; the callback returns it as reauth_token. A customer can also confirm with a code we email to the account's address, which works for an account with no password and no linked provider. POST /auth/reauth/code with {} sends it and answers a challenge. POST /auth/reauth/code/verify with the challenge and the code answers a reauth_token, which works for 5 minutes. A wrong, expired or used code answers 400 with detail.error set to wrong_code, expired or used, and wrong_code adds attempts_left. A support session cannot ask for a code.

Closing needs every service cancelled and no invoice unpaid or overdue. It removes the customer's name, email, phone, company, custom fields, social connections and password, ends every session, and cannot be undone. The answer is {"closed": true, "sessions_revoked": 2}.

GET /portal/notices returns the notices your staff published for this customer: those for everyone, and those for paying or free customers depending on whether this customer pays. Visitors who are not signed in get theirs from GET /storefront/notices.

Customers can follow their services, pay and download invoices, keep a balance, save cards and leave, all from your own pages. Your staff see the same records in the staff console.

401 on every call
The customer token is missing, expired or belongs to another organization. See Troubleshooting for customer sign-in.
Invoice not found or already paid
The invoice is paid, cancelled or not this customer's. Read it again with GET /portal/invoices/{invoice_id}.
404 Invoice not found from /delivery
The invoice is not this customer's. List the customer's own with GET /portal/invoices.
Change your email address from your profile.
PATCH /portal/billing-details received an email other than the account's own. Leave email out, or change the address with PATCH /portal/profile.
Use the two-letter code of a country, such as GB.
country_code is not a country we know. Send a code that GET /portal/countries lists.
No payment gateway available, No Stripe gateway available or No card gateway available
No gateway takes that currency for the customer's country. Try again with currency set to USD.
This way of paying takes US dollars only. Pay in USD or choose another way to pay.
square-pay was given a currency other than USD. Send USD, or draw the Stripe form for that currency.
Use square-pay to pay by Square
pay-intent was given a Square account. Send the token to square-pay instead.
That account is not a Square account
gateway_name on square-pay names a Stripe account or an account that is not ours. Leave gateway_name out.
409 from square-pay
Another payment for the invoice is still running or pending. Wait for the Retry-After seconds, read the invoice, and pay again only if it is still unpaid.
pending from square-pay
Square has not said whether it took the payment. Do not charge again. Read GET /portal/invoices/{invoice_id} until it is paid.
Payment currency TRY is not enabled
We do not take payments in that currency. Offer one that GET /portal/currencies lists.
412 with amount_changed
The amount changed after the quote, and we created nothing. Show the customer the amount in detail.quote and let them pay again with its charge_amount as expected_amount.
402 with the gateway's message
The bank declined the payment. Ask the customer to try another method.
Plan changes must keep the current billing cycle
The chosen plan bills on another cycle, and the service is not a free server. Offer only the plans GET /portal/services/{service_id}/plans returns.
Plan change already in progress
A request with the same Idempotency-Key is still running. Wait for it rather than sending again.
This service is set to end. Keep it first, then change its billing period.
A switch of billing period needs a service that is not ending. Take the cancellation back with POST /portal/services/{service_id}/keep, then ask again.
409 with not_cancelled from /keep
The service is not set to end, or the customer already kept it. Read it again with GET /portal/services/{service_id}: termination_date is null.
Service already cancelled
The service is ending or has ended.
The minimum top-up is $5.00
Send an amount from 5 to 10,000.
Pick one of the reasons listed or Tell us briefly what happened
Send a reason_code from reason_codes. With other, write at least 10 characters in reason_text.
409 when closing an account
The X-Close-Refused-Reason header says why: active_services, unpaid_invoices or already_closed. Cancel the services or settle the invoices first.
409 from a crypto call
Deposits on that network are not available right now. Offer another network from GET /portal/crypto/assets.

API operations on this page

MethodPathWhat it does
POST/api/v1/orgs/{org_slug}/portal/account/closeClose and anonymise the signed-in customer's own account
GET/api/v1/orgs/{org_slug}/portal/billing-detailsThe customer's billing details: name, company, country and address
PATCH/api/v1/orgs/{org_slug}/portal/billing-detailsChange the customer's billing details
GET/api/v1/orgs/{org_slug}/portal/countriesCountries a customer can bill from, and the one we think they are in
GET/api/v1/orgs/{org_slug}/portal/creditThe customer's credit balance and credit history, newest first
POST/api/v1/orgs/{org_slug}/portal/credit/topup/chargeCharge a saved payment method and add credit to the customer's balance
POST/api/v1/orgs/{org_slug}/portal/credit/topup/checkoutHosted checkout to buy USD credit for an org customer
POST/api/v1/orgs/{org_slug}/portal/credit/topup/confirm-paymentAdd credit for a card payment the customer finished in the page
POST/api/v1/orgs/{org_slug}/portal/credit/topup/pay-intentCreate a Stripe PaymentIntent to add credit in the page
GET/api/v1/orgs/{org_slug}/portal/credit/topup/pay-quoteThe card account and amount for adding credit in the page
POST/api/v1/orgs/{org_slug}/portal/credit/topup/paypal-captureCapture PayPal order and credit org customer balance
POST/api/v1/orgs/{org_slug}/portal/credit/topup/square-payAdd credit with a Square card token, in one request
GET/api/v1/orgs/{org_slug}/portal/crypto/addressThis customer's permanent deposit address for the network's family
GET/api/v1/orgs/{org_slug}/portal/crypto/assetsAssets this storefront can accept right now
POST/api/v1/orgs/{org_slug}/portal/crypto/checkLook for this customer's payment now, and say where it stands
GET/api/v1/orgs/{org_slug}/portal/crypto/depositsDeposit history, including transfers still confirming
GET/api/v1/orgs/{org_slug}/portal/crypto/deposits/{deposit_id}One deposit, for polling while it confirms
GET/api/v1/orgs/{org_slug}/portal/crypto/planEvery way to pay this amount in crypto, cheapest first
GET/api/v1/orgs/{org_slug}/portal/crypto/quoteHow much to send for a target USD credit, itemised
GET/api/v1/orgs/{org_slug}/portal/currenciesThe currencies this customer can pay in, with the exchange rate of each
GET/api/v1/orgs/{org_slug}/portal/geo-currencyThe country we place this request in, and the currency we suggest for it
GET/api/v1/orgs/{org_slug}/portal/invoicesNewest first, ties by id
GET/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}One of the customer's invoices, with what was paid against it
POST/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/apply-creditApply the customer's credit balance toward an unpaid invoice
POST/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/chargePay an invoice with a saved payment method
POST/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/checkoutCanonical hosted checkout for an org-customer invoice (alias of /pay)
GET/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/checkout-sessionResume an in-flight checkout after a new tab, login, or API restart
POST/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/confirm-paymentRecord a card payment the customer finished in the page, and answer how it settled
GET/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/deliveryHow far each store item and server on an invoice has got
GET/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/gatewaysGateway accounts that can checkout this invoice's pay currency
POST/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/payHosted checkout for an invoice (legacy path; prefer /checkout)
POST/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/pay-intentCreate a Stripe PaymentIntent to pay an invoice in the page
GET/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/pay-quoteThe card account and amount for paying an invoice in the page
POST/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/paypal-captureCapture paypal order
POST/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/paypal-stashPersist a PayPal order id so a redirect or reboot can still capture it
GET/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/pdfThe invoice as a document to keep
GET/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/refund-eligibilityMy invoice refund eligibility
POST/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/refund-requestAsk for the money paid on one of your invoices back
POST/api/v1/orgs/{org_slug}/portal/invoices/{invoice_id}/square-payPay an invoice with a Square card token, in one request
GET/api/v1/orgs/{org_slug}/portal/noticesLive notices for the signed-in customer, by whether they pay
GET/api/v1/orgs/{org_slug}/portal/payment-configGet portal payment config
GET/api/v1/orgs/{org_slug}/portal/payment-methodsList customer's saved payment methods
POST/api/v1/orgs/{org_slug}/portal/payment-methods/confirmConfirm payment method setup after customer approves on gateway side
POST/api/v1/orgs/{org_slug}/portal/payment-methods/setupInitiate payment method tokenization on a regional gateway account
DELETE/api/v1/orgs/{org_slug}/portal/payment-methods/{method_id}Remove (deactivate) a saved payment method
PUT/api/v1/orgs/{org_slug}/portal/payment-methods/{method_id}/defaultSet a payment method as the default for this customer
GET/api/v1/orgs/{org_slug}/portal/payment-preferenceThe customer's pay currency and billing country, and how each was set
PATCH/api/v1/orgs/{org_slug}/portal/payment-preferenceSave the customer's pay currency, billing country or auto-pay setting
PATCH/api/v1/orgs/{org_slug}/portal/profileAlias for PATCH /auth/me (account page compatibility)
GET/api/v1/orgs/{org_slug}/portal/refund-requestsList my refund requests
POST/api/v1/orgs/{org_slug}/portal/refund-requests/{request_id}/withdrawWithdraw one of your refund requests while it is still pending
GET/api/v1/orgs/{org_slug}/portal/servicesList my services
GET/api/v1/orgs/{org_slug}/portal/services/liveWhat the customer's servers are doing right now, in one request
GET/api/v1/orgs/{org_slug}/portal/services/{service_id}Get my service
POST/api/v1/orgs/{org_slug}/portal/services/{service_id}/cancelCancel one of the customer's own services, now or at the end of term
POST/api/v1/orgs/{org_slug}/portal/services/{service_id}/cancel-plan-changeCancel my pending plan change
GET/api/v1/orgs/{org_slug}/portal/services/{service_id}/cancel-previewWhat cancelling right now would return, before committing to it
POST/api/v1/orgs/{org_slug}/portal/services/{service_id}/change-planMove one of the customer's services to another plan
GET/api/v1/orgs/{org_slug}/portal/services/{service_id}/community-listingGet community listing
PUT/api/v1/orgs/{org_slug}/portal/services/{service_id}/community-listingUpsert community listing
PATCH/api/v1/orgs/{org_slug}/portal/services/{service_id}/gameproxyUpdate join-address branding (MOTD/favicon) for a service's sidecar gameproxy route
POST/api/v1/orgs/{org_slug}/portal/services/{service_id}/keepWithdraw a cancellation that is set for the end of the term, so the service keeps renewing
GET/api/v1/orgs/{org_slug}/portal/services/{service_id}/plan-previewWhat a plan change costs today, before committing to it
GET/api/v1/orgs/{org_slug}/portal/services/{service_id}/plansSizes this service can move to
GET/api/v1/orgs/{org_slug}/portal/transactionsList my transactions