Credentials
Create client credentials in API settings —/settings/api in your own app, which is
app.synq.io for EU, app.us.synq.io for US and app.au.synq.io for AU. Grant these scopes:
Getting Started covers exchanging them for an access token
and generating or installing a client — follow it rather than the short version, it is
generated from the API itself and stays current. Your region’s API endpoint is in the
region table.
What is specific to this guide is the environment it expects and the gRPC channel:
authorization: Bearer <token> on every call and refresh it when
expires_in elapses. Set the gRPC authority explicitly — without it the port travels in
the Host header and every RPC returns UNIMPLEMENTED with no message, which reads as a
missing service rather than a routing miss:
1. Types
OneUpsertType per kind of thing your tool has. Declare
traits — they decide how
your entities rank, sort and behave platform-wide.
1–1000 and workspace-wide, with no allocator. Call ListTypes first and
refuse to start if one of your numbers is taken under a different name — that is the only
protection against two integrations picking the same band.
2. Entities
OneUpsertEntity per object. Create every entity before anything points at one: a SQL
binding to an entity that does not exist is rejected at the write, and an entity group whose
members do not all exist is not a set anyone can reconcile against.
customID builds the identifier:
custom:
entity_id, use the synq_path variant instead — see
Finding the entity_id.
Annotations are your tool’s own filing system carried across, so the catalog can be filtered
the way the tool is browsed. Keep them to labels a person would pick out of a list; prose
belongs in the description, which accepts Markdown.
3. Schemas
Declare columns before any lineage step. ASELECT * expands only over an entity whose
columns are known, and a declared column edge only shows against a column the entity has.
Skip this and the table-level edges still appear, with nothing saying the column ones are
missing.
4. Lineage features
SqlDefinition, with bindings
The mechanism to reach for first. Give the SQL as the tool executes it, plus the dialect and adatabase_context for unqualified names.
A name in the SQL that is not a warehouse object — another custom entity, a queue, a
service — resolves to nothing and is silently dropped. references binds such a name to the
entity it stands for. Names that are warehouse objects need no binding.
ColumnLineage
For a component with no SQL at all. State each edge outright. The declaration is complete and replaces the previous one whole, so regenerate it from your tool’s metadata every run rather than diffing.Code and Git
Code carries the SQL, Python, YAML or JSON behind the entity for display; several per
entity. GitFileReference says where that code lives, so a connected
code integration can show recent commits against the entity
when it fails.
5. Relationships
For edges with nothing to say at column grain. Read the response — an edge is held between the two entities the endpoints resolve to, so two relationships that look different can be the same edge, and only the last write survives.6. Checks
Something that validates another entity is not a stage data flows through. Attach it with a check relationship, then give it aCheckCategory feature — attaching schedules no
re-publish of the derived check record, while writing a feature does, so the reverse order
leaves that record stale until the entity is written again.
7. The entity group
One group naming every entity your integration owns, every run. The server diffs it against the previous set and deletes what is missing, so an object deleted in your tool disappears from the catalog with no state kept on your side.ListEntityFeatures (leave entity_id unset for the whole workspace), ListRelationships and
ListCheckRelationships; diff it against what you are about to write; then call
DeleteEntityFeature, DeleteRelationships and DeleteCheckRelationships for what is gone —
before the group write, so no edge is left pointing at an entity that is about to go.
8. Executions
What gives a custom entity a status rather than just a position in a graph.created_at is
required and must be in the past, so a run that has not happened yet is simply not reported.
OK, WARN, ERROR or CRITICAL. UpsertLogEntry attaches log output without
changing status.
9. Read it back
Every way a custom integration goes wrong produces a valid-looking write and an empty graph, and the write side cannot tell the difference. End the program by reading its own lineage back. Lineage is computed asynchronously, so poll.nodeName picks the shape that says
what the node is — prefer the custom variant when present.
Check for the edges you expect, not merely that some edge came back. A declared column edge
naming a column the upstream does not have still produces one hop while the rest of the chain
is lost, so a non-empty result can still be a broken graph. Poll with a deadline, and exit
non-zero when it expires rather than logging and returning.
For column-level lineage, start from EntityColumnsStartPoint instead, naming the entity and
the columns. The response then populates column_dependencies, and each node’s
cll_details.cll_state says whether the parse succeeded. A state of RESOLUTION_FAILED on a
node is usually a name your SqlDefinition should have bound and did not.
This is not a first-version-only step. Keep it in the production integration.
Checklist before you ship
- Ids are lowercase slugs from the source system’s stable id, in
A-Z a-z 0-9 : _ . - - The title and any other human-readable text is in
name, never in an id - Every type declares its traits, and its number was checked with
ListTypesfirst - Every entity with columns declares its schema, before any lineage is written
- Every name in a
SqlDefinitionthat is not a warehouse object has a binding - Every upstream resolves, checked with
BatchResolveIdentifiers(check_existence: true) - Check entities send
packageandkind, and leave both categories unset - One group holds everything, sent whole on every run, never from a partial read
feature_idvalues are stable strings, not generated per run- The program reads its own lineage back and fails loudly when it is missing
Next
API surface
Every method, the scope it needs, and the limits
Agent workflow
A page to point a coding agent at, so it writes the integration for you