Skip to content

The JSON API

Every method of the Voplica API is one HTTPS POST of a JSON body, and its answer is JSON. Any HTTP client works: no SDK is needed. This is the unary JSON mode of the Connect protocol, so Connect clients work on the same host too, in JSON or in binary protobuf.

Terminal window
curl -X POST https://api.voplica.com/voplica.api.ai.model.ModelService/ListModels \
-H 'Content-Type: application/json' \
-d '{"limit": 2}'
  • The URL is https://api.voplica.com/<package>.<Service>/<Method>: the method’s full name from the API reference or the protobuf reference.
  • The method is always POST, with no query string.
  • The headers: Content-Type: application/json, and Authorization: Bearer <your key> when the method needs a key (Authentication). A charset=utf-8 parameter is accepted, and so is the Connect header Connect-Protocol-Version: 1, which you never need. A body may be sent compressed, with Content-Encoding: gzip.
  • The body is the request message in protobuf’s JSON form. {} is a request with every field unset.
  • The answer is 200 with Content-Type: application/json and the response message in the same form.

The request and the answer are the method’s protobuf messages, written as JSON the standard way:

  • Field names take either spelling: the JSON one (compatibleLoraModelFamily) or the one in the .proto file (compatible_lora_model_family), but not both for the same field. Answers use the JSON spelling.
  • Unknown fields are refused, with 400 and a message that names the field, so a misspelt field name never passes unnoticed — except inside a request map such as workflowInput (Generating images and videos).
  • 64-bit integers — timestamps, counts, credit amounts — are JSON strings in answers ("permanentCreditsMilli": "2500000"). In requests they are accepted as strings or as numbers. Ids are strings.
  • Enums are written by name ("FLUX_2_KLEIN_9B"). An unknown name is refused with 400.
  • Unset values are still printed in answers: a field that holds a plain value, a list or a map appears with its default (0, "0" for a 64-bit integer, "", false, [], {}, or the enum’s zero value). An optional field, a oneof field and a field that holds a message are left out when unset.
  • Bytes are base64.
  • The body must be one valid JSON object: no duplicate keys, no comments and nothing after the closing brace.

When a call fails, the answer is a JSON object with a code and a message, sent with a matching HTTP status:

{
"code": "invalid_argument",
"message": "invalid JSON request: Cannot find field: compatibleLoraModelFamilyy in message voplica.api.ai.model.GetRecommendedBaseModelVariantRequest"
}

Branch on code, not on the HTTP status: the status only follows the code.

code HTTP status For example
invalid_argument 400 An unknown field, a wrong value, a body over the limit.
out_of_range 400
unauthenticated 401 No key where one is needed, or a key that is not valid.
permission_denied 403 A method a key may not call.
not_found 404 The item does not exist (Model not found).
unimplemented 404
canceled, deadline_exceeded 408
already_exists, aborted 409
failed_precondition 412 A run that cannot start: not enough Anytime or Earned credits.
resource_exhausted 429 A per-account quota was reached.
unavailable 503 A temporary failure. Retry.
internal, unknown, data_loss 500

A timeout you set yourself with the Connect-Timeout-Ms header ends as 503, not 408. Treat every 503 as something to retry.

Retry a call that starts a run only with an idempotency key: ExecuteWorkflow takes idempotencyKey (letters, digits and _, at most 30 characters), and a retry with the same key returns the run already started instead of starting a second one.

A few refusals happen before your request reaches a method, at the host’s edge or in front of the service. They are not JSON: plain text, or an empty body.

HTTP status When
403 The method is not served on this host (not available on the public gateway).
404, empty The path is not a method of the API.
405 The request has a connect query parameter. Send a POST with the message in its body.
413 The body is far over the limit and is refused before it is read.
415 The Content-Type is not JSON, binary Connect (application/proto) or gRPC-Web.
503 A service is down (no healthy upstream). Retry.

So a client should read code from a JSON answer, and treat an answer that is not JSON by its HTTP status.

  • A request body is at most 1 MiB (1,048,576 bytes), counted after decompression. A larger body is refused with 400 and invalid_argument, and the message gives the limit. Files never travel inside a request: you pass their ids, such as those of your earlier runs’ outputs. Uploading files through the public API is planned.
  • Long work runs in the background. A call that starts a generation answers at once with an id, and you read the result with later calls, so no request has to stay open while the work runs.

Most list methods take a page size, limit, and answer with two tokens, nextToken and prevToken:

Terminal window
curl -X POST https://api.voplica.com/voplica.api.ai.model.ModelService/ListModels \
-H 'Content-Type: application/json' \
-d '{"limit": 2}'
{
"models": [{ "id": "…" }, { "id": "…" }],
"nextToken": "…",
"prevToken": "…"
}
  • The next page: send the same request with "cursor": "<nextToken>".
  • The previous page: send "cursor": "<prevToken>" and "before": true.
  • The end: a page with no items, and no tokens.

A few list methods page differently, for example by page number, and some return everything in one answer. Each method’s fields are in the reference.

Connect clients speak this protocol natively, in JSON or in binary protobuf (Content-Type: application/proto), and gRPC-Web clients work too. The native gRPC endpoint opens soon. Clients covers the clients for TypeScript, Java, Python and Go, which give you type-safe requests and responses for this API, and Protobuf and gRPC the definitions they are made from.