# Authentication (/docs/v0/authentication)
The Build API uses OAuth 2.0's **client credentials grant**. This is the standard way for one *service* to authenticate to another with no human in the loop — there's no login page, no browser redirect, and no user consent screen. If you've only used OAuth for "Sign in with X" buttons before, this is the same protocol used for a much simpler job: proving that *your server* is allowed to call *our API*.
At a high level:
1. You exchange a client ID and client secret for a short-lived **access token**.
2. You send that access token with every API request.
3. When it expires, you request a new one the same way.
There's no separate login step and no refresh token — getting a new access token *is* the refresh mechanism.
## 1. Get a client ID and secret [#1-get-a-client-id-and-secret]
VU.CITY provisions a client ID and client secret for you, along with the [scopes](#scopes) your client is allowed to request. Treat the client secret like a password:
The API has no CORS support, so it can't be called from browser JavaScript
anyway — but the same rule applies to your own code: never embed the client
secret in a mobile app, a single-page app, or any code that ships to a user's
device. Store it as a server-side secret.
## 2. Request an access token [#2-request-an-access-token]
Exchange your credentials for an access token at the token endpoint:
```
POST https://build.vu.city/oauth/token
```
The request must be `application/x-www-form-urlencoded` with `grant_type=client_credentials`. You can send your client ID and secret either as HTTP Basic auth, or as `client_id`/`client_secret` fields in the form body — pick whichever is more convenient for your HTTP client.
**HTTP Basic auth:**
```bash
curl -X POST https://build.vu.city/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=client_credentials
```
**Credentials in the form body:**
```bash
curl -X POST https://build.vu.city/oauth/token \
-d grant_type=client_credentials \
-d client_id=$CLIENT_ID \
-d client_secret=$CLIENT_SECRET
```
A successful response looks like this:
```json
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "volume:read"
}
```
`expires_in` is the token's lifetime in seconds. By default you're granted every scope your client is allowed to request; to ask for a smaller set instead, add a space-separated `scope` parameter to the request (e.g. `-d scope=volume:read`). Asking for a scope you don't hold returns an `invalid_scope` error rather than silently dropping it.
## 3. Call the API with your access token [#3-call-the-api-with-your-access-token]
Send the access token as a bearer token in the `Authorization` header of every request:
```bash
curl https://build.vu.city/v0/volumes \
-H "Authorization: Bearer $ACCESS_TOKEN"
```
That's the whole flow — see the [API reference](/docs/v0/api-reference) for the endpoints available to you.
## Token lifetime [#token-lifetime]
Access tokens are only valid for `expires_in` seconds (see the response above). There are no refresh tokens in the client credentials grant: when a token expires, request a new one the same way you got the first one, using your client ID and secret again. Most OAuth client libraries do this automatically — they cache the token and re-request it once it's close to expiring.
If VU.CITY revokes your client, no new tokens can be issued, but any token
already issued keeps working until it expires. Keep this in mind if you ever
need access cut off immediately.
## Scopes [#scopes]
An access token carries one or more **scopes**, each granting access to a specific capability. Every endpoint in the [API reference](/docs/v0/api-reference) documents which scope it requires.
| Scope | Grants |
| ------------- | ----------------------------------- |
| `volume:read` | List and read VU.CITY Drive volumes |
Calling an endpoint without the scope it requires returns a `403` with `insufficient_scope`, even if your access token is otherwise valid — see [Errors](#errors) below.
## Errors [#errors]
| Situation | Response |
| --------------------------------------------------- | -------------------------------------------------- |
| Wrong client ID or secret | `401` with `{ "error": "invalid_client" }` |
| Grant type other than `client_credentials` | `400` with `{ "error": "unsupported_grant_type" }` |
| Requested a scope your client isn't allowed | `400` with `{ "error": "invalid_scope" }` |
| Missing or malformed access token on an API request | `401 Unauthorized` |
| Valid token, but missing the required scope | `403` with `error: "insufficient_scope"` |
## Service discovery [#service-discovery]
If your HTTP client or library supports OpenID Connect discovery, you can point it at:
* `https://build.vu.city/.well-known/openid-configuration` — issuer metadata, including the token endpoint and supported scopes.
* `https://build.vu.city/.well-known/jwks.json` — the public key used to sign access tokens, if you need to verify them yourself.
# Getting Started (/docs/v0)
The **VU.CITY Build API** is our HTTP API for working with VU.CITY data from your own systems. Use it to pull platform data into your existing tools and workflows, or to bring your own data into VU.CITY, rather than working through the VU.CITY applications by hand.
## Closed alpha [#closed-alpha]
The Build API is currently in a **closed alpha**. It isn't generally available - we're rolling it out to a limited set of customers while it's still taking shape.
If you're interested in access, speak to your VU.CITY account manager. They
can tell you whether the API is a fit for what you're trying to do, but access
during the closed alpha isn't guaranteed.
## Get a client app [#get-a-client-app]
The Build API authenticates each caller as a **client app**, not as an individual user. If you've been given access, your account manager will arrange for a client app to be set up, and for a client ID and client secret to be issued to you - there's no self-service way to create one yet.
Once you have those, head to [Authentication](/docs/v0/authentication) to exchange them for an access token and make your first request.
## This API is at v0 [#this-api-is-at-v0]
The Build API is currently versioned `v0`, which marks it as **experimental**: breaking changes to request/response shapes, scopes, and behaviour are expected while we finish shaping it, and nothing under `v0` should be treated as stable.
A `v1` release is planned, and will be the stable version we'd recommend building a long-term integration against. Until then, keep in mind that:
* `v0` endpoints can change or be removed without the usual deprecation notice.
* Your integration should be able to tolerate updates, and it's worth checking this documentation before upgrading anything that talks to the API.
* When `v1` ships, `v0` will move to a deprecated state with a sunset date, rather than disappearing immediately - see the [API reference](/docs/v0/api-reference) for details on any given endpoint.
## Next steps [#next-steps]
* [Authentication](/docs/v0/authentication) - get an access token and make an authenticated request.
* [API reference](/docs/v0/api-reference) - browse the available endpoints.
# llms.txt (/docs/v0/llms-txt)
If you're using an LLM (ChatGPT, Claude, an AI coding assistant, etc.) to help you integrate with this API, you can hand it these docs directly instead of relying on whatever it already knows.
## What llms.txt is [#what-llmstxt-is]
[llms.txt](https://llmstxt.org) is an emerging convention for publishing documentation in a form LLMs can read directly: plain text and Markdown, without the navigation, styling, and page chrome an HTML page carries. Sites that support it publish one or more `.txt` files alongside their normal pages specifically for this purpose. This site publishes three:
| File | Contains |
| ---------------------------------------- | ---------------------------------------------------------------------------- |
| [`/llms.txt`](/llms.txt) | An index: every page on this site, with a short description and link. |
| [`/llms-full.txt`](/llms-full.txt) | The full content of every page on this site, concatenated into one document. |
| The **Copy Markdown** button on any page | Just that one page, as clean Markdown. |
## How to use it [#how-to-use-it]
The right one depends on how you're working:
* **Ask a quick question in a chat tool.** Open [`/llms-full.txt`](/llms-full.txt), copy it, and paste it in as context before you ask your question. It's the whole site, so the model has everything it might need in one go.
* **Working on one topic.** Use the **Copy Markdown** button at the top of the page you're on (next to the title) and paste just that page in - less noise for the model to wade through.
* **Using a tool or agent that can fetch URLs itself** (an IDE assistant, an agent with a browsing or fetch tool), just give it the address - `/llms.txt` if you want it to find its own way around, `/llms-full.txt` if you want it to have everything up front.
Feeding an LLM real documentation makes it far less likely to invent endpoints
or parameters that don't exist, but it doesn't make the answer authoritative.
Verify anything you get back against the [API
reference](/docs/v0/api-reference) before you rely on it, especially while the
API is at `v0` and still changing.
These files cover the whole site, not just `v0` - if a future version's docs are published alongside this one, they'll be in there too.
# Uploads (/docs/v0/uploads)
Some endpoints in the Build API let you upload a file to VU.CITY - for example, syncing an external file's contents to VU.CITY Drive. None of these endpoints accept the file's bytes directly. Instead, they all follow the same two-step pattern:
1. Call the endpoint as normal. Instead of the file, it returns an **upload URL**.
2. Upload your file's bytes directly to that URL, in a separate request.
This page explains that second step, which is the same for every upload endpoint in the API. For what a specific endpoint returns and any limits it applies (upload URL expiry, maximum file size), see that endpoint's own page in the [API reference](/docs/v0/api-reference).
## Why a separate upload step? [#why-a-separate-upload-step]
The upload URL points directly at the storage that will hold your file, rather than at the Build API itself. Your file's bytes go straight there instead of passing through the API.
The upload URL is what's usually called a **signed URL** (or "presigned URL"): a normal-looking URL with a long, random-looking query string attached. That query string *is* your permission to upload - it's cryptographically signed, and it only works for a short time (typically an hour; check the specific endpoint's docs for the exact figure). You don't need any of your usual API credentials to use it.
## Uploading your file [#uploading-your-file]
Send an HTTP `PUT` request to the upload URL, with your file's raw bytes as the entire request body - not wrapped in JSON, and not sent as an HTML form (`multipart/form-data`).
With `curl`:
```bash
curl -X PUT "$UPLOAD_URL" --data-binary @/path/to/your/file
```
`--data-binary` sends the file exactly as-is. Don't substitute `-d`/`--data` for a binary file (an image, a zip, anything that isn't plain text) - it can alter line endings and corrupt the upload.
In JavaScript, using `fetch`:
```js
await fetch(uploadUrl, {
method: 'PUT',
body: fileContents, // e.g. a Blob, a File, or a Buffer
})
```
Only send your Build API access token to Build API endpoints (anything under
`https://build.vu.city`). The upload URL already carries its own permission
in the URL itself, and doesn't need - or accept - an `Authorization` header.
A successful upload responds with a `2xx` status code and an empty body. There's nothing else to check: once that request succeeds, VU.CITY has the file.
## Things that trip people up [#things-that-trip-people-up]
* **Using `POST` instead of `PUT`.** The upload URL only accepts `PUT`.
* **Sending JSON or form data.** The request body must be the raw file - nothing wrapping it.
* **Waiting too long.** Upload URLs expire (see the issuing endpoint's docs for how long). If yours expires before you upload, call the endpoint again for a fresh one.
* **Reusing an upload URL for a different file, or a second time.** Request a new upload URL for each file you upload.
# API Reference (/docs/v0/api-reference)
This reference is split into sections by resource - use the sidebar to browse them.
## OpenAPI spec [#openapi-spec]
The full schema for v0 is also published as an OpenAPI document, if you'd rather import it into a client generator, a tool like Postman or Insomnia, or your own tooling.
[Download the v0 OpenAPI spec](/v0/openapi.json)
# Start a file sync (/docs/v0/api-reference/files/post)
# List volumes (/docs/v0/api-reference/volumes/get)