ResourceSyncer contract. The interface is focused: four methods per resource type, and the SDK handles everything else.
Your connector answers three questions:
- What exists? Users, groups, roles, applications
- What permissions are available? Entitlements that can be granted
- Who has what? Grants connecting users to permissions
Project structure
Directory layout
A common structure for Baton connectors:Not all connectors follow this exact structure. Some organize code differently based on their needs. The structure above is a common starting point, not a requirement.
baton-{service} - for example, baton-github, baton-okta, baton-salesforce.
Key files
Makefile targets
Standard connectors include these make targets:Setting up a new connector
1
Create repository
2
Add baton-sdk dependency
3
Create directory structure
4
Copy standard files
From an existing connector:
.golangci.yml(lint configuration)Makefile(build targets).github/workflows/ci.yaml(CI workflow).github/workflows/release.yaml(release workflow)
5
Implement the connector
Following the patterns in this guide
Capability manifest
The capability manifest declares what operations your connector supports. This file is auto-generated by running:Implementing ResourceSyncer
TheResourceSyncer interface is the heart of your connector. Per resource type, implement four methods:
ResourceType(ctx) *v2.ResourceTypeList(ctx, parentResourceID, token) ([]*v2.Resource, nextToken, annotations, error)Entitlements(ctx, resource, token) ([]*v2.Entitlement, nextToken, annotations, error)Grants(ctx, resource, token) ([]*v2.Grant, nextToken, annotations, error)
ResourceType()
Defines what this resource is:TRAIT_USER for people, TRAIT_GROUP for collections, TRAIT_ROLE for permission bundles.
List()
Fetches all instances of this resource type:The RawId annotation
Always include aRawId annotation with the external system’s stable identifier:
RawId to:
- Correlate resources across syncs - Same ID = same resource, not a duplicate
- Track provenance - Know which connector discovered which resource
- Enable pre-sync patterns - Support reservation mechanisms that create placeholders before sync
Entitlements()
Defines what permissions this resource offers:Grants()
Reports who has each entitlement:Pagination must progress: the SDK detects and errors if your “next page token” repeats the input token.
Modeling decisions
How you structure resources and entitlements determines what C1 can manage.What to sync as a resource?
Entitlement granularity
Fine-grained: Separate entitlements for read, write, admin- Pro: More control in access reviews
- Con: More complexity, more grants to manage
- Pro: Simpler model
- Con: Can’t revoke admin without revoking everything
Parent-child relationships
Some systems have hierarchies: organization -> project -> resource.- Child resources only make sense within a parent context
- You need to scope List() calls to a parent
- The target API is organized hierarchically
Definition of done
Your connector is ready when:- Sync works deterministically (same inputs produce stable IDs and consistent results across runs)
- Pagination works (no token loops; handles large datasets)
- You can run without production C1 credentials (local testing story exists)
Build and test
Common mistakes
Resource type mismatches
A grant references a principal byResourceId (type + id). If your principal type id doesn’t match what you used in ResourceType(), you will create dangling edges.
Implicit capability claims
A connector may have a--provisioning flag but still not implement specific provisioners. Treat “flag exists” as necessary, not sufficient.
API clients
If the service has an official Go SDK, use it. Otherwise, the SDK’suhttp package handles rate limiting and retries:
Error handling
Return errors, don’t panic. Wrap errors with context:Credentials
Never log credentials. Use the SDK’sSecretString type for sensitive config fields: