Lesson 7.2Lesson 7.2 · Scripting Revit & Dynamo
The Revit API Basics
The model as objects you can query - elements, parameters, transactions, and the collector that finds them
Every wall, door and sheet in a Revit model is already an object with a name, an id and a bag of parameters. The API is the door that lets your code reach in and read - or change - all of them.
A Revit model is not really a drawing - it is a database of objects. A wall is an object. It has an id, a type, a location, and a set of parameters: height, fire rating, comments. The same is true of doors, rooms, views and sheets. When you edit a value in Revit's properties palette, you are changing that object.
The Revit API (Application Programming Interface) is the published set of objects and methods that lets code do the same thing the interface does - find elements, read their parameters, and change them. This lesson is not an exhaustive tour; it is the four ideas that unlock everything else: what an element is, how parameters work, how the FilteredElementCollector finds the elements you want, and why every change must sit inside a transaction. Get these four and the model becomes something you can program.
Element -> parameters. Collector finds them. Transaction makes changes stick. That is the whole door.
Elements and parameters: the model as objects
In the Revit API, nearly everything in the model is an Element. Walls, doors, rooms, views, sheets, even wall types are elements. Every element has an ElementId - a stable numeric identity that never changes for the life of that object - which is how you refer to it unambiguously in code, and, as a later lesson shows, how you match a spreadsheet row back to the right wall.
What makes elements useful to script is their parameters. A parameter is a named slot holding a value: Comments, Fire Rating, Mark, Height. You read one by name with LookupParameter, then ask for its value in the right type, because Revit stores different parameters as text, numbers, yes/no flags or references to other elements:
p = wall.LookupParameter("Comments")
if p: # None if the parameter does not exist
text = p.AsString() # AsDouble / AsInteger / AsValueString / AsElementId
print(text)Two honest cautions save hours. First, `LookupParameter` returns `None` when the name does not exist - a different type, a typo, or a shared parameter that is not loaded - so you must check before using it. Second, Revit stores lengths internally in decimal feet regardless of your project units, so a wall height read with AsDouble comes back in feet even in a millimetre project; convert with UnitUtils when you need real-world units. Elements carrying parameters is the whole mental model: to automate Revit is mostly to find the right elements and read or write the right parameters on them.
One more distinction pays off early: parameters come in flavours. Built-in parameters are the ones Revit defines for every project - Comments, Mark, Fire Rating - reachable by name with LookupParameter or, more robustly, through the BuiltInParameter enumeration, which does not break if the interface language changes. Project and shared parameters are ones your template or a family author added, and a shared parameter must be loaded into the project before it exists to look up - a common reason LookupParameter returns None. You do not need to master the taxonomy now; just know that if a parameter you can see in Revit comes back as None in code, the name, the type, or a missing shared-parameter definition is usually why.
FilteredElementCollector: finding the elements you want
You rarely want every element - you want all the walls, or every sheet, or all the doors on one level. The tool for that is the FilteredElementCollector, the standard way the API searches the model. You start a collector on the document, then narrow it with filters, then pull the results into a list.
The two filters you will use constantly are by category and element-versus-type. A category is a kind of thing - OST_Walls, OST_Doors, OST_Sheets in the BuiltInCategory enumeration. And every category has both instances (the actual walls placed in the model) and types (the wall definitions in the family). WhereElementIsNotElementType() keeps the placed instances; you almost always want that:
from Autodesk.Revit.DB import FilteredElementCollector, BuiltInCategory
walls = (FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_Walls)
.WhereElementIsNotElementType()
.ToElements())
print(len(walls)) # how many walls in the modelWhere doc comes from depends on how you run the script, which the next section covers. The collector is efficient - it queries Revit's own database rather than looping over the whole model - and it composes: chain .OfClass(...) to filter by API class, or pass a level id to limit by level. Reading it aloud almost describes itself: collect from the document, of category walls, not the types, to elements. Once you can reliably gather exactly the set you mean, everything downstream - reading, auditing, changing - is just a loop over that set.
Transactions: why a change only sticks inside one
Reading the model is free. Changing it is not: the Revit API refuses to modify anything unless the change happens inside a transaction. A transaction is a named, all-or-nothing bundle of edits - it either commits as a whole or rolls back as a whole, which is what keeps the model consistent and gives you a single undo step. Try to set a parameter outside one and the API throws an error immediately.
The raw pattern is start, change, commit:
from Autodesk.Revit.DB import Transaction
t = Transaction(doc, "Set wall comments")
t.Start()
for w in walls:
p = w.LookupParameter("Comments")
if p:
p.Set("Checked 2026-08")
t.Commit()The name you give the transaction ("Set wall comments") is what appears in Revit's undo history, so make it descriptive. Notice that many edits sit inside one transaction - that is deliberate and fast; you do not start a new transaction per wall. How you open a transaction depends on your host. In a Dynamo Python node you do not use Transaction directly - you call TransactionManager.Instance.EnsureInTransaction(doc) before your edits and TransactionManager.Instance.TransactionTaskDone() after, letting Dynamo manage it. In pyRevit you use the raw Transaction above, or the tidy with revit.Transaction("name"): helper that starts and commits for you. The concept is identical everywhere: no change without a transaction, and one transaction can carry a whole batch of edits.
Where doc comes from: Dynamo vs pyRevit
Every script above assumed a variable doc - the open model. Getting it is the one piece that differs by how you run the code, and there are two common hosts worth knowing.
Dynamo you already met: a visual graph with a Python Script node, run from inside Revit. There, you reach the document through Dynamo's services:
import clr
clr.AddReference("RevitServices")
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocumentpyRevit is a different and very popular route: a free, open-source add-in that turns Python scripts into buttons on a Revit ribbon tab. You write a .py file, and pyRevit gives it a toolbar button; click it and the script runs against the live model. In pyRevit the current model is handed to you as __revit__:
doc = __revit__.ActiveUIDocument.DocumentWhich should you learn? They are complementary. Dynamo shines for graph-shaped, data-flow work and for people who like seeing the pipeline; pyRevit shines for turning a script into a reusable tool your whole office clicks like any other Revit command, and it is closer to writing plain Python. Both call the exact same Revit API - the same FilteredElementCollector, the same parameters, the same transactions - so nothing you learn here is wasted whichever you pick. Start with whichever your studio already uses; the four ideas transfer completely.
Same API everywhere. Only how you get doc (and open a transaction) changes by host.
You will always look things up - and that is normal
A quiet fear stops people using the Revit API: that you must memorise it. You do not, and no one does. The API is vast - thousands of classes and methods across dozens of namespaces - and even seasoned BIM programmers keep the documentation open constantly. Fluency here is not recall; it is knowing the shape of things well enough to find the specific method you need and read whether it does what you want.
The canonical reference is the Revit API documentation for your version, which lists every class, its methods and properties, and what each returns. Because the API changes between releases, the version matters - a method that exists in one year may be renamed or take different arguments in another, which is why an undated forum snippet sometimes fails. A widely used community resource, the RevitAPIDocs site, presents the same reference in a searchable form, and the Revit and Dynamo developer forums are where real problems get worked out. When you search, include the version and the class name; you are looking for the method signature - what it takes and what it gives back - not a finished script to paste.
There is also a fast way to explore the API from inside a running script: print what an object offers. Python's dir() lists the attributes and methods of any object, and type() tells you which class you are holding. Dropped into a Python node or pyRevit script, they turn abstract documentation into something concrete about the exact element in front of you:
wall = walls[0]
print(type(wall)) # the element's class
print([m for m in dir(wall) if not m.startswith("_")]) # its membersThis is not cheating; it is the normal working method. You form a rough idea from the docs, confirm it against a real object with dir and print, and adjust. Modern AI coding assistants (covered in Module 10) accelerate the first step - describe what you want and they suggest the collector or method - but they hallucinate API calls that do not exist, especially for the version you are on, so you still verify against the docs and a live object. The literacy that makes all of this work is exactly what this module builds: understand elements, parameters, collectors and transactions, and the rest is a lookup away. Nobody holds the whole API in their head, and you do not need to either.
Nobody memorises the API. dir() and type() reveal any object; the docs confirm it; AI suggests but you verify.
Element / ElementId
The objects in the model and their stable numeric ids
Almost everything is an Element; its ElementId never changes and is how you refer to it - and later match it to a spreadsheet row.
LookupParameter
Reads a named parameter off an element
Returns None if the parameter does not exist, so always check; then ask for the value with AsString/AsDouble and mind that lengths come back in feet.
FilteredElementCollector
The standard way to query elements from the model
Chain OfCategory and WhereElementIsNotElementType, then ToElements; it queries Revit's database efficiently rather than looping the whole model.
Transaction
An all-or-nothing bundle that any model change must sit inside
Start, edit, Commit; the API refuses changes outside one. Dynamo uses TransactionManager; pyRevit uses Transaction or a with-helper.
Workshop - count and read your model
A read-only script is the safest first API contact: it cannot damage anything, needs no transaction, and proves your collector works. You will count a category and print one parameter per element.
Revit with Dynamo, or Revit with pyRevit installed. Any model with a few doors will do - a sample or template file is fine.
Goal: collect a category, count it, and print one parameter each Inputs: any Revit model open in Dynamo or pyRevit Time: ~30 minutes
- 1Get the document:
DocumentManager.Instance.CurrentDBDocumentin a Dynamo Python node, or__revit__.ActiveUIDocument.Documentin a pyRevit script. - 2Build a FilteredElementCollector on
doc, filter with.OfCategory(BuiltInCategory.OST_Doors)and.WhereElementIsNotElementType(), and call.ToElements(). Print the count withlen(...). - 3Loop the doors and read each one's
Markparameter withLookupParameter, guarding againstNone. Collect the marks into a list and return or print it. - 4Spot the gaps: build a second list of the doors whose
Markis empty orNone. That is a missing-data audit - the seed of the next lesson. - 5Do NOT wrap any of this in a transaction - you are only reading. Confirm it runs with no transaction, then note where a transaction WOULD be needed if you changed a value.
You’ll walk away with
A read-only script that counts the doors in a model, lists their Mark values, and flags any door with a missing Mark - all without a transaction, proving your collector and parameter reads work.
Three altitudes on the same idea
Read the band that fits you — or all three.
The API turns the model into something you can audit and edit at scale. Instead of clicking through hundreds of elements, a collector gathers every wall or door in a line, a loop reads or checks a parameter, and one transaction writes a correction across the whole set. Model-checking - every door has a fire rating, every sheet a valid number - stops being a manual chore and becomes a script you run before each issue.
Your finishes, fixtures and furniture live as elements with parameters, and the API reaches all of them. Want every FF&E item's product code set from a rule, or a check that no room finish is blank? That is a collector over a category and a transaction that writes the values. You do not need to be a programmer - you need the four ideas here, and the model becomes a database you can tidy in seconds.
The Revit API is where BIM job listings and computational-design coursework actually point. Understanding elements, parameters, collectors and transactions is the vocabulary of every Revit automation you will read or write. Learn it against a small practice model, and you can talk credibly about BIM automation in an interview - and the same four ideas underpin pyRevit tools and Dynamo graphs alike, so the knowledge compounds across the whole ecosystem.
“The Revit API is only for professional software developers building plug-ins - it is out of reach for a designer.”
Do it yourself
Check the four ideas.
- 1What does
LookupParameterreturn when the parameter name does not exist, and why must you check for it? - 2Write the three collector steps that gather all the placed walls (not wall types).
- 3Why does reading a wall's height with
AsDoublesometimes surprise you in a millimetre project? - 4What happens if you try to set a parameter outside a transaction?
- 5Name one thing Dynamo and pyRevit each do better, given both call the same API.
The one line to carry out
Peer-reviewed journals & authoritative standards
- 01Autodesk Revit — Wikipedia, 2026.
- 02Application programming interface — Wikipedia, 2026.
- 03Building information modeling — Wikipedia, 2026.
- 04Dynamo (software) — Wikipedia, 2026.
With the four ideas in hand, we can stop reading and start doing. Next: real automations - renaming views and sheets, setting parameters in bulk, placing tags and auditing for missing data - the drudgery BIM scripting exists to remove.
The author
Amogh N P
Architect, interior designer, and creative polymath. Studio Matrx began in his notebooks — his vision of design made honest, useful, and open to everyone. Its Academy is written and taught in his memory, and free, forever.
More about Amogh →