REST API
Content may include inaccuracies, outdated information, or technical errors. Users are advised to cross-check critical information before implementation.
Architeezy exposes a REST API under /api on the same host as the application.
It covers scopes, projects, models, representations, project membership and
users, and it serves model content in every format the server can write.
The generated OpenAPI description is at
/v3/api-docs, and an interactive browser
for it at
/swagger-ui/index.html.
Authentication
Read requests work without credentials and return the entities that are public. Everything else - creating, updating, deleting, and reading entities with a higher confidentiality level - requires a signed-in caller. There are two ways to sign in.
Session cookie
An application served from the same host as Architeezy inherits the browser
session. Send the cookie by adding credentials: "include" to your requests:
const response = await fetch('/api/users/current', { credentials: 'include' });
const user = response.status === 204 ? null : await response.json();
GET /api/users/current answers 204 No Content for an anonymous caller and
200 with the user profile for a signed-in one. That is how an application
finds out at startup whether the user is signed in.
Bearer token
Tools that run outside the browser - a script, a desktop application, a CI job
- authenticate with an OpenID Connect access token and send it as a header:
GET /api/models HTTP/1.1
Host: architeezy.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAi...
The public client to use is architeezy-api. It accepts the authorization code
flow with loopback redirect URIs (http://localhost:* and
http://127.0.0.1:*). A local tool opens the system browser, catches the
redirect on a port it listens on, and exchanges the code for tokens.
Ask for the offline_access scope in addition to openid and you receive an
offline refresh token. It survives the end of the browser session, so an
unattended job can issue fresh access tokens with nobody signed in. Store it
like a password.
The endpoints and the client id are published, so a client can discover them instead of hard-coding them:
$ curl https://architeezy.com/api/settings
{"aboutUrl":"https://about.architeezy.com",
"documentationUrl":"https://docs.architeezy.com",
"applicationsUrl":"https://apps.architeezy.com",
"oauth2IssuerUrl":"https://auth.architeezy.com/realms/architeezy",
"oauth2ApiClientId":"architeezy-api"}
The issuer URL serves the standard OpenID Connect discovery document at
/.well-known/openid-configuration, which carries the authorization and token
endpoints.
Resources
| Path | Resource |
|---|---|
/api/scopes | Scopes, the top-level containers for projects |
/api/projects | Projects, their imports and their members |
/api/models | Models, their metadata and their content |
/api/representations | Diagrams, tables, forms and documents |
/api/project-images | Images stored in a project |
/api/project-users | Project membership |
/api/project-enrollments | Requests to join a project, and their approval |
/api/users | User profiles, and the current user |
Entities are addressed by their UUID, for example
GET /api/models/019b151f-aa97-77b2-9a08-c676443c4a00. Projects, models and
representations can also be addressed by their slug chain, which is what you see
in the browser address bar:
| Pattern | Resolves to |
|---|---|
/api/projects/{scope}/{project}/{version} | A project |
/api/models/{scope}/{project}/{version}/{model} | A model |
/api/representations/{scope}/{project}/{version}/{model} | Its default representation |
/api/representations/{scope}/{project}/{version}/{model}/{repr} | A representation by slug |
HAL responses
Collections and single entities are returned as HAL+JSON: the entity fields plus
a _links map, and for collections an _embedded map keyed by the resource
name.
curl 'https://architeezy.com/api/models?size=2&sort=name,asc'
{
"_embedded": {
"models": [
{
"_links": {
"self": {
"href": "https://architeezy.com/api/models/019b151f-aa97-77b2-9a08-c676443c4a00"
},
"content": [
{
"href": "https://architeezy.com/api/models/demo/eip-system-design/dev/airline-ticket-aggregator/content?format=json{&inline}",
"templated": true,
"title": "JSON",
"type": "application/json"
}
]
},
"id": "019b151f-aa97-77b2-9a08-c676443c4a00",
"scope": { "id": "019a8255-...", "slug": "demo", "name": "Demo" },
"project": {
"id": "019b12f8-...",
"slug": "eip-system-design",
"version": "dev",
"name": "EIP System Design"
},
"slug": "airline-ticket-aggregator",
"name": "Airline Ticket Aggregator",
"confidentiality": 0,
"contentType": "https://architeezy.com/metamodel/eip/dev/eip#EnterpriseIntegrationModel",
"defaultRepresentation": {
"id": "019b151f-ac0b-7a0e-aab7-78040deac6db",
"slug": "enterpriseintegrationmodel-diagram",
"name": "EnterpriseIntegrationModel Diagram"
},
"creator": { "id": "27ebbd12-...", "name": "denis" },
"creationDateTime": "2025-12-13T00:32:33.974480Z",
"lastModificationDateTime": "2025-12-13T01:27:55.940772Z"
}
]
},
"_links": {
"first": {
"href": "https://architeezy.com/api/models?page=0&size=2&sort=name,asc"
},
"self": {
"href": "https://architeezy.com/api/models?page=0&size=2&sort=name,asc"
},
"next": {
"href": "https://architeezy.com/api/models?page=1&size=2&sort=name,asc"
},
"last": {
"href": "https://architeezy.com/api/models?page=19&size=2&sort=name,asc"
}
},
"page": { "number": 0, "size": 2, "totalElements": 39, "totalPages": 20 }
}
Each model carries one content link per output format the server can produce
for it. The links are URI templates: strip the {&inline} part, or fill it in,
before you fetch them.
Pagination
Lists take page (zero-based) and size, and answer with a page block
holding number, size, totalElements and totalPages. Rather than
computing page numbers, follow _links.next until it is absent:
async function fetchAllModels() {
const models = [];
let url = 'https://architeezy.com/api/models?size=100';
while (url) {
const response = await fetch(url, { credentials: 'include' });
const page = await response.json();
models.push(...(page._embedded?.models ?? []));
url = page._links?.next?.href ?? null;
}
return models;
}
Sorting
sort=<field>,<direction> orders the result, for example sort=name,asc or
sort=lastModificationDateTime,desc. Repeat the parameter to sort by several
fields in order.
Filtering
Any other query parameter is read as a filter on a field of the resource. For
models the fields include id, slug, name, description,
confidentiality, contentType, creationDateTime,
lastModificationDateTime, and the dotted forms project.slug,
project.version, project.name, scope.slug, scope.name, creator.name
and lastModifier.name.
The value carries the operator:
| Value | Meaning |
|---|---|
name=Order | Equal to Order |
name=!Order | Not equal to Order |
name=Order* | Starts with Order, ignoring case |
name=*Order* | Contains Order, ignoring case |
name=!*Order* | Does not contain Order |
confidentiality=>=2 | Greater than or equal to 2, also >, <, <= |
description= | Empty or absent |
description=! | Present |
Repeating a parameter combines the conditions with AND. For non-text fields a
comma inside one value combines them with OR, so confidentiality=0,1 matches
either. Commas in a text value are literal. Date-time fields are the exception:
their comma-separated parts combine with AND, which is how you express a range.
Naming several fields in the key applies one condition across all of them and matches when any field satisfies it:
curl 'https://architeezy.com/api/models?name,description=*payment*&size=20'
An unknown field is an error rather than a silently ignored parameter:
{
"detail": "Invalid filter parameters",
"instance": "/api/models",
"status": 400,
"title": "Bad Request",
"errors": [{ "message": "Unknown filter field: nope", "field": "nope" }]
}
Model content
Metadata and content are separate resources. GET /api/models/{id} returns the
metadata; GET /api/models/{id}/content returns the serialized model, with the
same endpoint available on the slug chain:
curl 'https://architeezy.com/api/models/demo/eip-system-design/dev/airline-ticket-aggregator/content?format=json'
{
"json": { "version": "1.0", "encoding": "utf-8" },
"ns": { "eip": "https://architeezy.com/metamodel/eip/dev/eip" },
"content": [
{
"id": "961dae55-b6f7-53bd-8d80-cd9f70320f93",
"eClass": "eip:EnterpriseIntegrationModel",
"data": {
"name": "Airline Ticket Aggregator",
"entities": [
{
"id": "350261b6-fad7-52d8-a8a8-80cb9206e661",
"eClass": "eip:Endpoint",
"data": { "name": "API Gateway" }
}
]
}
}
]
}
The format comes from ?format= or from the Accept header. Add ?inline=true
to have the response displayed by the browser instead of downloaded.
format | Content type | Contents |
|---|---|---|
json | application/json | The model as JSON |
xmi | application/vnd.xmi+xml | The model as XMI |
ttl | text/turtle | The model as RDF Turtle |
trig | application/trig | The model as RDF TriG |
archimate | application/xml | An ArchiMate model in Archi's own file format |
ifc | application/x-step | An IFC model as a STEP file |
Every model can be written as json, xmi, ttl and trig. The
notation-specific formats are offered only for the models they fit: archimate
for an ArchiMate model, ifc for an IFC model. A request for a format the
server cannot produce for that model answers 406 Not Acceptable.
Two endpoints answer questions about a model instead of returning a file.
GET /api/models/{id}/owl returns the OWL ontology derived from the model, and
POST /api/projects/{id}/sparql runs a SPARQL query across a project.
Writing content
PUT /api/models/{id}/content?format=archimate replaces a model's content with
the raw body of the request. To create a model and fill it in one call, post
multipart/form-data to /api/models with a JSON part named entity and the
file in a part named content:
$ curl -X POST https://architeezy.com/api/models \
-H "Authorization: Bearer $TOKEN" \
-F 'entity={"projectId":"019b12f8-f501-7553-9f35-4cde33edea0c",
"name":"Ordering"};type=application/json' \
-F 'content=@ordering.archimate'
The upload format is taken from the file extension unless you set
contentFormat in the entity part. A format no deserializer accepts answers
415 Unsupported Media Type.
Status codes
200 on a read or update, 201 on a create, 204 on a delete. 400 marks a
malformed request or a failed validation, 409 a conflict such as a slug
already taken, 415 an unsupported upload format, 406 an unavailable output
format.
401 means an anonymous caller tried to write. 403 means you can see the
entity but may not change it. 404 covers both an entity that does not exist
and one you are not allowed to see, so probing identifiers tells you nothing
about what exists.
Errors use application/problem+json. A 400 or 409 carries an errors
array with the offending fields; a 500 carries an errorId you can quote to
whoever runs the server.