Skip to main content
You are building a Go module that implements the ResourceSyncer contract. The interface is focused: four methods per resource type, and the SDK handles everything else. Your connector answers three questions:
  1. What exists? Users, groups, roles, applications
  2. What permissions are available? Entitlements that can be granted
  3. Who has what? Grants connecting users to permissions
The Baton SDK handles orchestration, output format, pagination coordination, and communication with C1. You focus on translating your system’s API into the Resource/Entitlement/Grant model.

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.
The naming convention is 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:
Example manifest:
Do not write this file manually. Always generate it from the connector binary to ensure accuracy.

Implementing ResourceSyncer

The ResourceSyncer interface is the heart of your connector. Per resource type, implement four methods:
  • ResourceType(ctx) *v2.ResourceType
  • List(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:
Traits tell C1 how to interpret the resource. Use TRAIT_USER for people, TRAIT_GROUP for collections, TRAIT_ROLE for permission bundles.

List()

Fetches all instances of this resource type:
Return a page of resources plus a token for the next page. Empty token means you’re done. The SDK calls you repeatedly until you return an empty token.

The RawId annotation

Always include a RawId annotation with the external system’s stable identifier:
Why this matters: C1 uses the 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:
Users typically return empty here - they receive grants, they don’t offer entitlements.

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
Coarse-grained: Single “access” entitlement
  • Pro: Simpler model
  • Con: Can’t revoke admin without revoking everything
Choose based on how access decisions are made. If “can this person admin the database?” is a real question, admin should be a separate entitlement.

Parent-child relationships

Some systems have hierarchies: organization -> project -> resource.
Use hierarchies when:
  • Child resources only make sense within a parent context
  • You need to scope List() calls to a parent
  • The target API is organized hierarchically
See Pagination patterns for handling hierarchical data with nested pagination.

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 by ResourceId (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’s uhttp package handles rate limiting and retries:

Error handling

Return errors, don’t panic. Wrap errors with context:

Credentials

Never log credentials. Use the SDK’s SecretString type for sensitive config fields:

Quick reference

Resource traits

Method return signatures

SDK helpers