Everything you need to use Dbrij Source: clone and push, access tokens and the dbrij credential helper, pull requests and reviews, push refusals, the .dbrij/ci.yml pipeline, runners, CI identity tokens and deploying to Hosting.
Dbrij Source is git hosting with CI built in. Repositories are real git repositories spoken to over git's own smart HTTP protocol, so git clone, git fetch, git pull and git push work with no plugin and no wrapper. Every branch, commit, file and diff you see in the browser is read from the repository at the moment you look, never from a second copy that could drift.
Source stands on its own for private team repositories, and it is also where Dbrij Hosting deploys from and where Dbrij Forge writes the apps it builds. A push can run your pipeline, deploy a service, move tasks on a project board and post a card in a chat channel, all without a callback URL to register.
https://api.dbrij.com/gitA repository answers at https://api.dbrij.com/git/<owner>/<repo>.git. The owner segment is your Dbrij handle (or a stable u- name when you have not set one), and it never changes for the life of the account, so a URL pasted into a script keeps working.
1 · Create a repository. In Source, press New repository and give it a name. Leave it empty to push an existing project into it, or start it with a README so the first clone has something in it. The default branch is main unless you choose another.
2 · Sign git in. The quickest way is the Dbrij command line, which makes git sign in as you with no token to copy:
npx dbrij login
npx dbrij git setupPrefer a token? Make one under Source settings and use it as the password, with your Dbrij handle as the username (see Signing git in).
3 · Push or clone. An empty repository shows the push commands; one with history shows the clone command.
git remote add dbrij https://api.dbrij.com/git/ada/website.git
git push -u dbrij maingit clone https://api.dbrij.com/git/ada/website.gitAdd a .dbrij/ci.yml file and the next push runs it (see The pipeline file). Point a Hosting service at the repository and every push to its branch deploys (see Deploying to Hosting).
Git carries a credential in exactly one way, HTTP basic auth, so Source reads a username and a password. The username is your Dbrij handle, the same word as the first part of your clone URLs. The password is a Source access token. When git has no credential, Source answers 401 with a challenge so git asks for one (or asks its credential helper).
Make tokens on the Source tokens page. Every token starts dbs_, so a leaked one is recognisable on sight. Only a SHA256 hash of it is stored: the token is shown once, when it is made, and never again. The list shows each token's first characters, when it was last used and when it expires, so the right one can be revoked.
WHAT A TOKEN CAN BE
| name* | string | 1 to 80 characters, for telling tokens apart. |
| repoId | string | Confine the token to one repository. Leave it out to reach every repository you can reach. |
| canWrite | boolean | False (the default) makes the token fetch only. True lets it push where you may push. |
| expiresInDays | number | 1 to 365. Leave it out for a token that does not expire. An expired token is treated as no token. |
A token confined to one repository needs the Manage action on that repository. A token never reaches further than its owner: if you lose access to a repository, so does every token you made.
With a person's token, the username must be that person's handle. Typing another name is refused with 403 and a message naming the right one, because it usually means a credential helper saved a colleague's token. Machines may instead send one of these usernames, which are never checked against a person: x-access-token, git, oauth2 or token. A token pasted as the username with an empty password is accepted too.
# The token as the password, a machine username in front of it
git clone https://x-access-token:$DBRIJ_SOURCE_TOKEN@api.dbrij.com/git/ada/website.git
# Or as a header, so the token never sits in a URL
git -c http.extraHeader="Authorization: Bearer $DBRIJ_SOURCE_TOKEN" clone https://api.dbrij.com/git/ada/website.gitA bearer token in the Authorization header is read as machine access, with no username to check.
dbrij git setup makes the Dbrij command line git's password helper for Dbrij Source only. When git needs a password, the command line asks Dbrij for a short lived push token using the sign in it already has, and git never prompts. Nothing is pasted, and a prompt cannot hide in an editor window and leave a push hanging.
npx dbrij login # sign the command line in, once
npx dbrij git setup # make it git's helper for Dbrij SourceWhat it does, exactly:
credential.https://api.dbrij.com.helper in your global git config, for that one host only. An empty entry goes first, so helpers inherited from elsewhere (a system keychain, an editor prompt) are cleared for this host alone and an old pasted token is never offered ahead of a fresh one.dbrij git-credential when the dbrij on your PATH is this same program, otherwise the exact version through npx. Running setup again is safe.git config --global --unset-all credential.https://api.dbrij.com.helperNot signed in? The helper prints a note telling you to run dbrij login and lets git ask for a token the usual way.
A repository belongs to the person who made it, and it can also belong to a company. Access follows from that:
| Owner | person | The person who made the repository: reads, pushes, merges, changes settings, makes repository tokens and deletes it. |
| Source in the company | company | In a company repository, members who hold the Source product in that company read, push, merge and change settings. |
| A grant on one repository | company | A grant names its actions: view to read and clone, push to push and manage branches, review to merge, manage to change settings and CI secrets. |
| Company owner | company | May delete any repository made in the company. |
A personal repository is reachable by its owner alone. A repository you cannot see and a repository that does not exist answer git with the same 404, so guessing names reveals nothing.
A repository has a display name of up to 80 characters and a URL name (its slug) made from it: lower case letters, numbers, dots, underscores and dashes, up to 60 characters. These slugs are reserved: git, info, objects, refs, new, settings. Renaming a repository never moves its data on disk, and a description holds up to 500 characters.
Source is included with no charge of its own. Repository bytes count against the same storage allowance as your drive and Storage buckets, and the check happens when bytes arrive, at the push. On the Free plan pushes stop at the included amount; a paid plan keeps going past it, metered, up to five times what it includes. A push that does not fit is refused with 507 Insufficient Storage and the reason, which git prints at your terminal. Clone and fetch keep working.
Repositories can be filed under a category of your own (Backend, Mobile, a client's name) of up to 60 characters, and Source home groups them by it. Categories are free text on purpose; leave it empty for uncategorised.
The browser shows files, history (also filtered to one path), each commit's diff, blame, branches and a contribution graph. Text over 512 KB is cut short for display; binary files are offered as downloads. A plain language summary of a commit is available on demand. Source writes backup bundles of repositories and checks their integrity on a schedule.
Only git's smart HTTP protocol is served. A clone or push with nothing moving either way for 15 minutes is ended, and no single transfer runs longer than 3 hours.
Push branches as usual, or make one in the browser from any existing ref (the default branch unless you choose another). A repository needs a commit before a branch can start from it. Branch names are up to 120 characters and must be names git accepts.
The default branch is what the browser opens, what pull requests target unless told otherwise, and what deploys follow unless a service names another branch. Change it in the repository settings. The default branch cannot be deleted: change the default first, then delete the old one. Making and deleting branches needs push access.
There is no separate protected branch setting. What guards a branch is who holds push access, and the merge rules in Pull requests and reviews.
A pull request proposes merging a source branch into a target branch (the default branch unless you name another). Its commits, its diff and whether it still merges are all read live from the repository, so an approval is always of what is there now.
OPENING ONE
| title* | string | 1 to 160 characters. |
| body | string | Up to 5,000 characters. |
| sourceBranch* | string | The branch with the work. |
| targetBranch | string | Where it lands. Defaults to the default branch. |
Refused: a branch into itself, a branch that does not exist, a branch with nothing on it the target lacks, and a second open pull request for the same pair. Numbers count up per repository.
A review is a comment, an approval or a request for changes, on the whole change or on one file, with up to 5,000 characters. Reviews are append only: each person's latest approval or request for changes is their position, and comments never displace one. A review written before newer commits landed is marked stale, and the pull request says when every approval predates the current tip.
Merging needs push access, or a grant with the review action. The API checks, at the moment of merging:
A merge always makes a merge commit, with the message Merge pull request #<n> from <branch> and the title, or your own message (up to 1,000 characters), recorded under the name of the person who merged. You can delete the source branch as part of the merge. A merge counts as a push to the target branch, so it runs CI and deploys like one.
Closing leaves the branch alone. The author or anyone with push access may close; a closed pull request reopens while its source branch still exists. A merged one cannot be closed.
Every refusal is sent as plain text, so git prints it line by line, prefixed remote:, at your terminal instead of a bare status code.
| 401 | Unauthorized | No credential, or a token that is wrong, revoked or expired. Carries the challenge that makes git ask for one. |
| 403 | Wrong username | The username is not the handle of the token’s owner. The message names the right one. |
| 404 | Not found | The repository does not exist, you cannot see it, or the token is confined to a different repository. All three read the same. |
| 403 | Read only | The token cannot push, or you may read this repository but not push to it. |
| 403 | Dumb protocol | Only the smart HTTP protocol is served. |
| 503 | Pushes paused | Pushing is paused on the account that owns the repository. Clone and fetch still work. |
| 507 | Insufficient Storage | The push does not fit in the storage allowance of the repository’s owner (see Repositories and storage). |
| pre receive | Secret found | In a repository set to block, a push that adds a secret is rejected with the file and line (see Secrets in pushes). |
$ git push
remote: Your Free plan includes 5 GB of storage across your drive and buckets. Pick a plan in Storage billing for more.
fatal: unable to access 'https://api.dbrij.com/git/ada/website.git/': The requested URL returned error: 507The figure in that message is your own plan's allowance; the example shows the shape. A push is only announced to CI, deploys and the rest of Dbrij after git accepted it, so a refused push never starts anything.
Source reads the lines each push adds for keys and passwords, with the same rules dbrij secrets scan runs on your own machine. Each repository chooses what a push that adds a secret meets:
| warn | default | The push lands, and the pusher and the repository owner are told. The finding is listed on the repository. |
| block | mode | The push is refused before any branch moves, with the file and line, so the secret never reaches the repository. |
| off | mode | Nothing is read. |
A finding shows a redacted start of the value, never the secret. When the value is a Dbrij key that still works, the finding says so and links to where it can be revoked. Mark a finding resolved once the secret is replaced or revoked, or dismissed when it is not a secret (a test value, an example). Changing the mode needs the Manage action on the repository. If the scanner itself fails, the push goes through and is scanned after it lands, so a broken scanner never locks a team out of its code.
Source CI reads .dbrij/ci.yml at the commit a push moved a branch to. When the file is there and names the branch, a run starts with one job per job in the file. No file, no run.
on:
branches: [main, "release/*"]
jobs:
test:
name: Tests
image: node:22-bookworm
timeout: 20
env:
NODE_ENV: test
steps:
- npm ci
- name: Unit tests
run: npm test
- name: Build
run: |
npm run build
ls dist
lint:
steps:
- npm ci
- npx eslint .| on.branches | string | string[] | Branches that run the pipeline: exact names, or a start of one ending in * ("release/*"), or * alone. Leave on out to run on every branch. At most 20. |
| jobs* | map | At least one job, at most 10. Each key is the job's id: letters, numbers, dashes and underscores, up to 40 characters. |
| name | string | Up to 80 characters. Defaults to the job id. |
| image | string | The public container image the steps run in. Defaults to node:22-bookworm. |
| timeout | number | Minutes, 1 to 60. Defaults to 20. |
| env | map | NAME: value lines, at most 50. A name is letters, numbers and _, not starting with a number, up to 100 characters. |
| steps* | list | At most 30. Each is a command, or a name and run pair. A step is up to 20,000 characters. |
A STEP
| run* | string | The command. Use | for several lines; they run as one script. |
| name | string | Up to 80 characters. Defaults to the first line of run. |
The file is at most 64 KB. It reads nested settings and lists by indentation (spaces, not tabs), plain and quoted values, [a, b] lists and | or > blocks for commands over several lines. Anchors, aliases, tags and { } mappings are refused with a plain reason, as is any setting the file does not read. One document per file.
These names are set by Dbrij for every job and may not be used in env or as CI secrets: CI, and anything starting DBRIJ_CI or DBRIJ_CLONE.
A file with problems still records a run, with status error and every problem listed by line or by setting, and the person who pushed is told. Fix the file and push again.
Jobs have no order between them and no dependencies on each other. There is no cache and no artifact storage between jobs or runs: each job starts from a fresh checkout. Runs start from pushes (and from Run again); there is no schedule trigger.
Each branch a push moves (up to ten per push) is checked against on.branches and gets its own run. Merging a pull request counts as a push. Run numbers count up per repository. People with push access can cancel a run or run it again on the same commit; running again is refused while the run is still going, or when the file is no longer in that commit.
/workspace folder of up to 10 GB./workspace/repo, the working folder for every step. The checkout has its own 3 minutes on top of the job's timeout, and only it holds the clone token.bash -eo pipefail when the image has bash, otherwise sh -e. After a step fails, the rest are skipped and the job fails with that step's exit code.SET IN EVERY JOB
| CI, DBRIJ_CI | "true" | Always set. |
| DBRIJ_CI_REPOSITORY | string | owner/name of the repository. |
| DBRIJ_CI_BRANCH | string | The branch the run is for. |
| DBRIJ_CI_SHA | string | The commit being tested. |
| DBRIJ_CI_RUN_ID | string | The run id. |
| DBRIJ_CI_RUN_NUMBER | string | The run number in this repository. |
| DBRIJ_CI_TOKEN_URL | string | Where the job asks for its identity token (see CI identity tokens). |
| DBRIJ_CI_REQUEST_TOKEN | string | The job’s own request token for that call, valid only while the job runs. |
Each repository holds up to 50 CI secrets of up to 32,768 characters, set in the repository's CI settings by people who manage it. A value is never shown again once saved; the list shows names, when each changed and who changed it. Secrets reach the steps as environment variables (never the checkout), after the file's own env, so a secret wins over an env line of the same name. Any secret value of four or more characters is replaced with *** in the output.
Each step's status, exit code and duration are shown, and the output marks where each step began and ended. The last 200,000 characters of a job's output are kept.
| queued | run, job | Waiting for room. |
| running | run, job | Started. |
| passed | run, job | Every step exited 0. |
| failed | run, job | A step exited with an error, or the job ran past its timeout. |
| error | run, job | It could not run: a problem in the file, an image that could not be pulled, a failed checkout, no room on the cluster, minutes used up. |
| cancelled | run, job | Someone cancelled it. |
A run fails when any job failed. The person who pushed is told when their run failed or could not run; passing runs are quiet.
CI runs on the Hosting cluster, so like a deploy it needs a verified card or a paid Hosting plan. Minutes are counted, not charged: each space gets a monthly allowance (a calendar month in UTC) from the Hosting plan that governs it, and a job past the allowance waits for the next month or a higher plan.
| Hosting plan | CI minutes a month |
|---|---|
| Free | 100 |
| Starter | 500 |
| Pro | 2,000 |
| Reserved | 5,000 |
A running job can prove who it is without holding a long lived key. It asks for an identity token: a JWT signed by Dbrij (RS256) that names the repository, the branch, the commit and the run, in the shape OpenID Connect providers use. Dbrij Secrets trusts it out of the box, and anything that reads OpenID Connect can check it with the published keys.
curl -s -H "Authorization: Bearer $DBRIJ_CI_REQUEST_TOKEN" \
"$DBRIJ_CI_TOKEN_URL?audience=https://secrets.dbrij.com"{
"value": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6…",
"expiresAt": "2026-11-03T09:10:00.000Z"
}Leave audience out for https://secrets.dbrij.com. A token lives 10 minutes; ask again when you need another. The request token works only while its job runs.
CLAIMS
| iss | string | https://api.dbrij.com/api/v1/public/source/ci |
| sub | string | repo:<owner/name>:ref:refs/heads/<branch> |
| aud | string | The audience asked for. |
| iat, nbf, exp, jti | standard | Issued at, not before, expiry and a unique id. |
| repository, repository_id | string | owner/name, and the repository id (stable across renames). |
| ref, sha | string | refs/heads/<branch> and the commit. |
| run_id, run_number, job | string, number | The run, its number, and the job id from the file. |
| actor_id | string | null | Who pushed, when a person did. |
PUBLIC ADDRESSES
| Discovery | GET | https://api.dbrij.com/api/v1/public/source/ci/.well-known/openid-configuration |
| Keys | GET | https://api.dbrij.com/api/v1/public/source/ci/jwks |
| Token | GET | https://api.dbrij.com/api/v1/public/source/ci/token?audience=…, with Authorization: Bearer <request token> |
Signing keys are replaced every 90 days, and a replaced key keeps verifying for a day. The token address is shared by every job and allows 600 calls a minute in all.
In Dbrij Hosting, make a service from a Source repository. The service tracks one branch, the default branch unless you choose another. When a push lands, every service built from the repository deploys its branch: no callback URL to register and no secret to rotate, because the push and the deploy happen inside Dbrij.
A repository can scan its default branch with AI on every push. It is off unless you switch it on in the repository settings, because each scan spends AI credits and takes a few minutes; the Run scan button is always there instead. It applies to repositories no Hosting service deploys from.
Source has no outgoing webhook setting. A push reaches the rest of Dbrij directly, inside the platform, once git has accepted it: CI, Hosting deploys, project boards, chat channels and notifications all hear the same push.
Connect a repository to a Dbrij Chat channel and choose which events post a card:
| pr.opened | on by default | A pull request was opened. |
| pr.reviewed | on by default | A review was left. Mentions the author when changes are requested. |
| pr.merged | on by default | A pull request merged. |
| pr.closed | on by default | A pull request closed without merging. |
| push | on by default | New commits on the default branch. |
Connect a Dbrij project to the repository and its tasks get a key such as CIRVA. A commit that mentions CIRVA-12 moves that task to in progress; one that says fixes CIRVA-12 (or fix, fixed, close, closes, closed, resolve, resolves, resolved) closes it. The project owner can also require a merged pull request on every task before it is submitted.
In a company repository, a push to the default branch notifies everyone who holds Source in that company, except the person who pushed. Personal repositories notify nobody. A CI run that failed or could not run tells the person who pushed.
| Repositories | per owner | 200. |
| Repository bytes | per account | Your storage allowance (see Repositories and storage). Past 50 GB of repositories, making new ones asks you to talk to us first. |
| Transfers | per clone or push | 15 minutes with nothing moving, 3 hours at most. |
| Token expiry | days | 1 to 365, or none. |
| Command line push token | hours | 12. |
| Pipeline file | size | 64 KB. |
| Jobs, steps | per file | 10 jobs, 30 steps a job, 20,000 characters a step, 50 env lines a job, 20 branch patterns. |
| Job timeout | minutes | 20 by default, 60 at most, plus 3 for the checkout. |
| Job size | each | 1 CPU, 2 GB memory, 10 GB workspace. |
| Jobs at once | per space | 2. |
| CI secrets | per repository | 50, 32,768 characters each. |
| Job output kept | characters | 200,000. |
| Identity token | minutes | 10. |
Dbrij is Nigeria’s company operating system: the first Nigerian built platform to put team chat, video meetings, email, HR, tax compliant payroll, customer support, marketing, analytics and cloud hosting in one product on one subscription.