OpBuilder is a convenience around OperationState. In particular if you know the C++ class of the Operation you want to build (the common case), OpBuilder exposes a template method: builder.create<OpClass>(...) which is managing the OperationState and the dispatch to a specific builder for the OpClass: https://github.com/llvm/llvm-project/blob/master/mlir/include/mlir/IR/Builders.h#L390-L402
If you don’t have a C++ class to call the template, you can use OperationState with a string to identify the op.
Another aspect is that OpBuilder keeps an “insertion point” and will automatically insert the operation in the block at the current position, while OperationState has to manage the insertion manually.
It also holds a context, so it can provide shortcuts for constructing common types and attributes.
Generally, C++ code is expected to use OpBuilder unless it has a strong reason not to. Since you are looking at C API, I suppose you want to provide bindings for another language. It’s up to those bindings to define what is the most idiomatic to the host language way to build the IR.