8. API keys

Create keys on the panel's Developer API page (/api). The plaintext is shown once — store it. Each key has an optional per-day cap (max_per_day; 0 = server default).

Action Where
Create / list / pause / delete panel /api page (session-authenticated)
Verify a key + read balance GET https://mp.dvocorp.com/api/v1/me/ping with the key

Signing in to the panel (to get that session)

Two doors, one account: email + password, or Continue with Google. A Google sign-in creates the ordinary account (same starter credits, same plans, same keys); signing in with Google using an address that already has an account takes you into that account rather than making a second one.

Action Endpoint
Password sign-in POST https://mp.dvocorp.com/api/v1/auth/login {email, password, captcha_token?}
Start a Google sign-in POST https://mp.dvocorp.com/api/v1/auth/google/start {next, mode}{authorization_url}
Finish it (browser returns with ?code=&state=) POST https://mp.dvocorp.com/api/v1/auth/google/callback {code, state}{access_token, created}
Set / change your own password POST https://mp.dvocorp.com/api/v1/auth/password {current_password?, new_password}
Attach / detach Google POST / DELETE https://mp.dvocorp.com/api/v1/auth/google/link
Register with an email POST https://mp.dvocorp.com/api/v1/auth/register {email, password, full_name?, captcha_token?}
Is this address usable? POST https://mp.dvocorp.com/api/v1/auth/email/check {email}{acceptable, verdict}
Send / resend the confirmation code POST https://mp.dvocorp.com/api/v1/auth/email/send-code
Confirm with the code POST https://mp.dvocorp.com/api/v1/auth/email/verify {code}{email_verified, granted_credits}

Confirming your email

Registration works with any mailbox, not just Gmail. The account is created and fully usable immediately — studio, API keys, uploads, everything. What waits for confirmation is the welcome credits.

Nothing is emailed by /auth/register. The 6-digit code is sent when you ask for it (/auth/email/send-code, which is what the banner's Confirm button calls) — a message sent during signup is one that ages out unread. Entering the code credits the signup bonus and subscribes you to job notifications and product news. /auth/email/status reports code_pending (one is already in the inbox, so don't send another) and resend_available_in (seconds until the next send is allowed).

Signing in with Google skips this — Google already verified the address, so the credits are granted right away.

Because open signup is the one endpoint that costs us money per request, it is guarded: a Cloudflare Turnstile captcha (when configured — sign-in carries the same check, since that is where leaked-password lists get tried), a per-IP rate limit, an address check (shape + does the domain accept mail + no disposable inboxes), and hard limits on how often a code can be sent or guessed. verdict from /auth/email/check is one of ok | invalid | no_domain | disposable | unknown (unknown = our DNS could not answer, and the address is still accepted).

The captcha's public site key, whether confirmation is available and what it pays are all in GET https://mp.dvocorp.com/api/v1/public/config (turnstile_site_key, email_verification_enabled, verify_bonus_credits).

Credit history lives at GET https://mp.dvocorp.com/api/v1/billing/ledger?limit=&offset=&entry_type= (panel session; entry_type: purchase | debit | refund | signup_bonus | adjust | subscription | expire) — the same data the /ledger page shows.

Plans & credits

Everything below needs a panel session (the access_token from /auth/login), not an API key — buying is a browser flow. To read your balance with a key, use GET https://mp.dvocorp.com/api/v1/me/ping instead.

# 1. what you can buy — prices are already personalised for YOUR account
curl -H "Authorization: Bearer $JWT" https://mp.dvocorp.com/api/v1/billing/plans
# -> [{"id":2,"slug":"pro","name":"Pro","price_cents":1900,"currency":"USD",
#      "period_credits":6000,"period_days":30,"priority":5,
#      "is_locked_price":false,"list_price_cents":1900}]
# 2. start a plan purchase -> open `redirect_url` in a browser to pay
curl -X POST -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"plan_id":2}' https://mp.dvocorp.com/api/v1/billing/subscribe
# -> {"order_id":"ord_...","redirect_url":"https://nowpayments.io/payment/...",
#     "status":"waiting","balance":100}
# 3. or buy a one-off credit pack (these never expire)
curl -X POST -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"package_id":1}' https://mp.dvocorp.com/api/v1/billing/checkout
# 4. your balance, and how much of it renews
curl -H "Authorization: Bearer $JWT" https://mp.dvocorp.com/api/v1/billing/balance
# -> {"balance":6100,"expiring":6000,"expires_at":"2026-09-03T10:00:00Z"}

GET https://mp.dvocorp.com/api/v1/billing/subscription returns the active term (plan_name, period_end, credits_per_period, auto_renew) or null.

How credits behave. You have one balance. Plan credits are granted each paid period and are void at its end — an unused remainder is replaced by the new allowance, not added to it. Credits you buy as a pack, and any an administrator grants you, never expire. A charge always consumes the soonest-expiring credit first, so nothing you paid extra for is lost to a reset. expiring in the balance response is the part that lapses on expires_at; it is not a separate wallet.

The free tier is a daily allowance, not a plan. There is no free plan to sign up for and no feature is switched off without one. Every confirmed account is topped up with a small number of credits once a day, usable on anything the API offers. Like plan credits they are refreshed rather than accumulated — yesterday's unused allowance is replaced by today's, not added to it, so the daily figure is also the most you can hold. Confirming your email grants a larger one-off amount on top (granted_credits in the confirm response). Ledger entries are typed daily_free, and the top-up stops while a paid plan is active, since a plan already grants its own allowance.

Practical consequence for API clients: the free tier is fine for steady low-volume work and cannot do bursts. If you need to process a batch in one go, the credits have to come from a plan or a pack.

Renewals are manual. Crypto payments cannot be pulled from a wallet on a schedule, so auto_renew is always false and nothing is ever charged without you starting it. Buy the next term from /pricing before the current one ends. POST /billing/subscribe on the plan you already hold simply starts a new term.

Your price does not go up. The price and allowance you first pay for are locked to your account for that plan. If the list price later rises, you keep yours (is_locked_price: true, with list_price_cents showing what new customers pay). If it falls below yours, you get the lower one.

Payment states. status follows waiting → confirming → paid. Credits land only on paid, which is driven by the provider's callback — expect a few minutes for network confirmations after the redirect returns. An underpaid invoice becomes partially_paid and is not credited automatically; contact support with the order_id. expired and failed grant nothing and charge nothing.

You never lose a payment to a lost callback. If the provider's callback never reaches us, open payments are polled independently and settled from the provider's own record, so a paid order is credited either way — occasionally a couple of minutes later than usual. Poll GET /billing/balance rather than assuming the redirect meant delivery. If an order is still waiting 48 hours after payment, that is the point to contact support with the order_id.

Plans and packs are hidden until payments are live. GET /billing/plans and GET /billing/packages return [] when no payment provider is configured, so you will never be quoted a price you cannot actually pay.