Candor Deploy v1.2.48
-
Persistent volumes for apps. A project can now hold volumes: disk that keeps an app’s files (uploads, a SQLite database) across deploys and restarts. Create one with a name, a size and a mount path, and attach it to one app or worker; detach it (the files stay) or delete it (the files are gone, with no snapshot), both after typing its name. You can raise a volume’s size allowance but never lower it; raising it doesn’t add space to a disk that is already full. Volumes and buckets share your plan’s storage allowance: Free 1 GB (no more), Solo 5 GB, Studio 25 GB, Org 75 GB, PAYG 10 GB. Storage past that isn’t billed yet; it will be billed at $0.02 per GB-month later (up to each plan’s cap). While a volume is attached, a deploy stops the old instance before starting the new one, so expect a few seconds of downtime. A volume lives on one server and isn’t replicated or backed up. The REST API is under
/v1/orgs/{slug}/projects/{key}/volumes(and/v1/orgs/{slug}/volumesfor every project), and the MCP server has newlist_volumes,create_volume,resize_volume,attach_volume,detach_volumeanddelete_volumetools. Deleting needsapp.delete. -
Buckets: private, S3-compatible object storage in a project (coming soon). Buckets are not available yet. Once they are, create one in a project (“New > Bucket” in the portal,
POST /v1/orgs/{slug}/projects/{key}/buckets, or the MCP’screate_bucket). Buckets are private. Attaching one to an app writesAWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEY(marked secret),AWS_REGION,AWS_ENDPOINT_URL_S3,S3_ENDPOINTand the bucket’s name (S3_BUCKET, or a name you choose) into its environment, so any S3 SDK works without pasting keys. Redeploy the app to apply them. Neither the API nor the MCPattach_buckettool ever returns the keys. The bucket page has a Files browser with short-lived download links. Deleting a bucket removes every file in it and needs the bucket’s name typed again; it takesapp.delete(owners and admins), as deleting a database does. Listing and browsing takeapp.database.read, creatingapp.create, attachingapp.database.manage. Buckets count against the same storage allowance as volumes (egress is free). A new bucket is refused once storage is at the plan’s cap or its bucket count is reached. A suspended project, or one being deleted, can still delete its buckets. MCP tools:list_buckets,create_bucket,attach_bucket,delete_bucket. -
Viewers no longer see database passwords. Below Developer, a database’s connection details (portal,
GET /v1/services/{id}/connectionand the MCPget_connectiontool) show host, port, database and username, and ascheme://host:port/databaseaddress with no user or password, for Postgres, MySQL and Supabase alike. The password and any credentialed URI are withheld, and for a Supabase so are the service-role key and the Studio password; its API URL and anon key are still shown. This applies to viewers and to API tokens created by a viewer, and the connection details carry a note that the rest needs the newapp.database.credentialspermission (Developer or above). Developers, admins and owners still get the full credentials, and each full read is recorded in your organization’s audit log. An app’s environment screen (and MCPlist_env) now masks, for everyone, any value that is a URI with a password in it, such as aDATABASE_URLattached before 30 September that was stored unmasked. Your app still receives the value unchanged. Read the full DSN from the database’s connection details. Masking only changes what is shown: saving the environment, from the editor or with MCPset_env, keeps such a value exactly as it was stored, so preview deployments keep inheriting it. Only theuser:password@form is detected. A password you saved unmasked yourself in some other form, such as a plainDB_PASSWORDvariable or?password=in a URI, is still shown as you saved it, so mark it secret. -
Storage usage meter on organization billing. The organization’s billing page shows “Storage: X of N GB used” for volumes and buckets together, split by project and by volume or bucket, and how far it is over the allowance now. Overage isn’t billed yet; it will be billed at $0.02 per GB-month later (Free has no overage: it is capped at its allowance). Until then the overage accrued this billing period reads “not available” (
null) rather than $0. Database disk is listed on its own line and doesn’t count toward the allowance.GET /v1/orgs/{slug}/storagereturns the same figures; members see their own projects, and only organization billing readers see the accrued overage fields. The MCPwhoamitool reports it on each organization asorganizations[].storage(allowance_gb,used_gb,overage_gb,overage_gb_months,overage_usd). -
Your plan belongs to your organization. The organization’s billing page and
GET /v1/orgs/{slug}/planshow one plan for the organization, one status, its pool (RAM, apps, databases and storage allowance, with what’s in use) and each project’s share of it. Status uses one set of words everywhere: Active, Trial (before billing is set up, running on the included credit), Past due, Suspended, Cancelled, so an invoiced project no longer reads “Not subscribed” next to “Current plan”. Owners, admins and billing members change the plan for every project at once withPOST /v1/orgs/{slug}/billing/plan. Moving one project to another plan is refused once an organization has more than one project. Members without billing access see whether projects are running, paused or cancelled, but not past due or trial. The MCPwhoamitool reports the plan on each organization (organizations[].plan) and moves the old workspace layer tointernal_workspaces, markedinternal. The top-levelplan/quota_mb/tenant_idfields stay for older clients. -
Organization spend cap, with emails at 75% and 90%. An organization can now set one monthly cap on usage charges across all of its projects, on the portal’s Billing page. The MCP server’s new
get_spend_capandset_spend_captools need an organization token, which the MCP doesn’t accept yet, so use the portal for now. Owners, admins and billing members can change it; anyone who can see billing can read it. Owners and billing members get an email when usage reaches 75% and again at 90% of the cap. When it reaches the cap, we pause the projects that are billing usage (nothing is deleted) and send one email listing them. They start again within about 10 minutes of the cap being raised or removed, or when the next month begins. Plan fees don’t count toward the cap. A project’s own spend cap still works, as an optional budget within the organization’s. -
Billing is per organization. A new organization bills on one card for the organization; per-project billing can no longer be chosen when you create an organization or switch to it later (
POST /v1/orgsandPOST /v1/orgs/{slug}/billing-modeanswer 400 for"project", unless the organization is already on it, which is a no-op). An organization already on per-project billing keeps working, and can still move to organization billing while it has at most one project and no billing yet. Asking for the billing mode an organization already has now answers 200 and changes nothing.GET /v1/me, and so the MCPwhoamitool, reports billing once per organization in a newbillingfield (scopeand a one-linesummary, which says “invoiced to the organization by contract” for invoiced organizations) and never per project.billing_modethere is deprecated. -
Invoices on the organization’s Billing page.
GET /v1/orgs/{slug}/billing/invoices(needsorg.billing.read: Owner, Admin or Billing) lists the organization’s finalized invoices, newest first, with date, total, amount still owed, status (open, paid, void, uncollectible) and links to the PDF and Stripe’s hosted page. Where each project pays for itself, the invoices of every project the caller holdsproject.billing.readon are merged and each names its project; if Stripe can’t be read for some of them, the rest still come back withincomplete: true.?limit=takes 1 to 100 (default 24) andhas_moresays older ones exist. The MCP’s new read-onlylist_invoicestool reads the same list but needs an organization token, which the MCP doesn’t accept yet. Cards are still added or replaced only in the dashboard. -
API tokens live in your organization’s Settings. Tokens belong to an organization, so you now create and revoke them under the organization’s Settings → API tokens in the portal instead of under Account. Account keeps your identity and password and links to each organization’s tokens. The MCP server’s instructions, its “Not authorized” error, the connector sign-in page and the
candor loginprompt point to the new place and say that an organization owner or admin creates them. They (and both token managers) also say to limit the token to one project, which the MCP connection and the CLI still need. The connector approval page no longer names a place to revoke it. The sign-in page’s “Get a token” button still opens Account, which links to each organization’s tokens. Existing tokens keep working unchanged. -
Clearer wording: “app” instead of “service”, “GB RAM” for memory. Errors from the API and the MCP server’s tool titles, descriptions and errors now say “app” (or “app or database”, “worker”, “static site” as fits) where they said “service”, for example “App and worker limit reached (3).”, “Unknown kind: ‘wrkr’.” and “Custom domains apply to apps and static sites only.”. Memory sizes in tool descriptions say “GB RAM”, and the delete tools’ titles say they remove databases too. Attaching a database that is not in the project now answers “No such database.” Tool names, arguments and response fields are unchanged (
create_service,service_id,scope="service"still work). The monitoring tools point atwhoamiwhen an organization slug may be wrong. Turning on single-origin routing is no longer refused because a worker or a database shares the project’s name (they serve nothing at that host). -
One place for all your custom domains. A project’s Domains tab and your organization’s Domains index list every custom domain with the app it points at, the DNS record to create, whether DNS resolves there yet and whether its certificate has issued. You can add a domain to an app or to a project’s single origin from there, and removing one there asks you to type its name again. The MCP has matching
list_domains,add_domainandremove_domaintools (remove_domainneeds the hostname repeated as confirmation). Plainhttp://to every domain, new ones included, redirects tohttps://. Managing domains needsapp.domains.manage(Developer or above) on every app the domain serves, and every add and removal made in the portal, the API or the MCP is recorded in your organization’s audit log.candorhost.appitself (not just names under it) and internal names (.local,.internal,.svcand similar) can no longer be added, and a domain can only be in use once across all of Candor. With Cloudflare for SaaS on, a domain that has no Cloudflare hostname is now told to point at the edge, which is what serves it. -
Attaching a database now needs permission to manage it. Attaching one, or a
.candorfiledeploy that wires in the project’s existing database, now needsapp.database.manage(Developer or above) on that database; otherwise attach answers 403 and the deploy fails with the reason. A push or staff redeploy may only wire in the app’s own database, and is refused for any other. -
Deploys finish even when your whole memory pool is in use. A deploy normally starts the new version next to the old one and switches over once it is ready, so your app never stops serving. That needs room in your pool for a second copy of the app for a moment. With all of the pool allocated (for example the trial pool’s 256 MB app and 512 MB database) there was no room, and the deploy stayed on “deploying” forever while the old version kept running. Now, when the room is not there, the old version stops first and the new one starts in its place. Your app is briefly unavailable while it starts (usually seconds), and never uses more memory than your plan. If the new version doesn’t come up, the deploy is marked failed with the reason; roll back to the last good deploy to restore it. To keep switch-overs seamless, leave free memory in your pool at least the size of your largest app. A deploy whose new version doesn’t fit in the pool even on its own now fails with a message saying so, instead of waiting, and the message says whether memory or your plan’s limit on running instances is what ran out. A deploy replaced by a newer one before it finished is now shown as canceled, rather than taking on the newer deploy’s result.
-
HTTPS certificates are issued and renewed even when your whole memory pool is in use. Let’s Encrypt checks a hostname by briefly running a small helper next to your apps. That helper counted against your memory pool, so with all of the pool allocated it was refused. A new app or custom domain then stayed on an untrusted certificate, and an existing certificate could not renew. The helper no longer uses your pool.
-
An app connected to a repository by its
owner/reponame now builds on every deploy, not just the first. A later deploy, Redeploy, rollback or preview of a public repository failed with “does not appear to be a git repository”. Those builds now clonehttps://github.com/owner/repo.git. -
Repositories are saved in one form. A GitHub repository given as
owner/repo,github.com/…,www.github.com/…, a GitHub URL, or with.git, a trailing/or@branch, is saved ashttps://github.com/owner/repo.git(@branchbecomes the branch). A repository on another host is given as its full clone URL and saved as is. A bareowner/repoalways means GitHub. A value with spaces, or one that names no repository (a/b/c,host.example/a/b, a GitHub/tree/link,file://), is refused with a 422 that says why. -
Every deploy of a new commit now honours your
.candorfile’skind,memory,envanddatabase, not just the first one. A portal deploy, a git push, Redeploy and a staff redeploy all re-read them at the commit being built:kind: appon a static site turns it into an app. It keeps its domains, and moves to 512 MB unless the file names amemory. The app starts in place of the static site before the static site is removed, so a failure leaves the site serving.- Other kind changes (app to static, to or from a worker) aren’t done on
deploy. It deploys as the kind it is, and an audit event records
that
kindwasn’t applied and why. Create a new one of the other kind to switch. - An explicit
memoryresizes the app. envkeys the app doesn’t receive yet, from its own env or the project’s shared env, are added.{generate: …}secrets are minted only if missing, so existing values are never rotated, overwritten or shadowed.- A
databasethe app isn’t wired to yet is attached from the project’s existing database of that engine, or provisioned if there is none.
If the project’s memory pool can’t hold the app, the deploy fails with that reason and the app is left as it was. A rollback, and a Redeploy after a rollback, rebuild the older commit without re-shaping the app.
-
Apps can call other apps’
*.candorhost.appaddresses again. A request from inside an app to any hostname served by the platform (your own other app, a teammate’s preview, the API) was refused with “connection refused”. It now connects. -
Supabase connections now include the Postgres details. The connection view,
GET /v1/services/{id}/connectionand the MCPget_connectiontool now return the stack’s own Postgres for a Supabase database:- host (the in-project
supabase-<name>-dbaddress), port 5432, and database and userpostgres(the stack’s admin role, not a superuser); - for Developer and above only, the password and a ready-made
database_url(postgresql://…). Viewers get the address without credentials, as described above.
The
uriis still the Supabase API URL. The anon key is unchanged, and so is the service-role key for Developer and above. In the portal’s Connect drawer the API URL is shown unmasked and labelled as such, and the Postgres details sit in their own section with the password andDATABASE_URLmasked. Attaching a Supabase still sets onlySUPABASE_URLand the two keys. If an app needs SQL access, copydatabase_urlinto itsDATABASE_URLyourself. - host (the in-project