Skip to content

REST API

Use the Artifacts REST API to manage repos, remotes, forks, imports, and tokens from external systems.

Review Namespaces first, then choose the namespace name you will use in these API paths.

Base URL and authentication

Artifacts REST routes use this base path:

https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/artifacts/namespaces/$ARTIFACTS_NAMESPACE

Requests use Bearer authentication:

Authorization: Bearer $CLOUDFLARE_API_TOKEN

Route paths below are shown relative to /accounts/$ACCOUNT_ID. Curl examples use ARTIFACTS_BASE_URL or ARTIFACTS_ACCOUNT_BASE_URL to keep commands shorter.

The following examples assume:

Terminal window
export ACCOUNT_ID="<YOUR_ACCOUNT_ID>"
export ARTIFACTS_NAMESPACE="default"
export ARTIFACTS_REPO="starter-repo"
export CLOUDFLARE_API_TOKEN="<YOUR_API_TOKEN>"
export ARTIFACTS_BASE_URL="https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/artifacts/namespaces/$ARTIFACTS_NAMESPACE"
export ARTIFACTS_ACCOUNT_BASE_URL="https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/artifacts"

All JSON responses use the standard Cloudflare v4 envelope:

{
"result": {},
"success": true,
"errors": [],
"messages": []
}

Successful blob, file, and raw responses return file bytes directly instead of JSON. For example, GET /artifacts/namespaces/:namespace/repos/:name/file?ref=main&path=README.md returns the contents of README.md with Content-Type: application/octet-stream. Error responses still use the standard envelope:

{
"result": null,
"success": false,
"errors": [
{
"code": 10200,
"message": "File not found"
}
],
"messages": []
}

Returned repo tokens are secrets. Do not log them or store them in long-lived remotes unless your workflow requires it.

Shared types

TypeScript
export type NamespaceName = string;
export type RepoName = string;
export type BranchName = string;
export type Scope = "read" | "write";
export type TokenState = "active" | "expired" | "revoked";
export type ArtifactToken = string;
export type Cursor = string;
export type RepoSortField =
| "created_at"
| "updated_at"
| "last_push_at"
| "name";
export type SortDirection = "asc" | "desc";
export interface ApiError {
code: number;
message: string;
documentation_url?: string;
source?: {
pointer?: string;
};
}
export interface CursorResultInfo {
cursor: string;
per_page: number;
count: number;
}
export interface OffsetResultInfo {
page: number;
per_page: number;
total_pages: number;
count: number;
total_count: number;
}
export type ResultInfo = CursorResultInfo | OffsetResultInfo;
export interface ApiEnvelope<T> {
result: T | null;
success: boolean;
errors: ApiError[];
messages: ApiError[];
result_info?: ResultInfo;
}
export interface RepoInfo {
id: string;
name: RepoName;
description: string | null;
default_branch: string;
created_at: string;
updated_at: string;
last_push_at: string | null;
source: string | null;
read_only: boolean;
}
export interface RepoWithRemote extends RepoInfo {
remote: string;
}
export interface TokenInfo {
id: string;
scope: Scope;
state: TokenState;
created_at: string;
expires_at: string;
}

Namespaces

List namespaces

Route: GET /artifacts/namespaces?limit=&cursor=

Use the account-level base URL.

Terminal window
curl "$ARTIFACTS_ACCOUNT_BASE_URL/namespaces?limit=20" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"

Get a namespace

Route: GET /artifacts/namespaces/:namespace

Terminal window
curl "$ARTIFACTS_ACCOUNT_BASE_URL/namespaces/$ARTIFACTS_NAMESPACE" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"

Repos

Create a repo

Route: POST /artifacts/namespaces/:namespace/repos

Request body:

  • name RepoName required
  • description string optional
  • default_branch BranchName optional
  • read_only boolean optional

Response type:

TypeScript
export interface CreateRepoRequest {
name: RepoName;
description?: string;
default_branch?: BranchName;
read_only?: boolean;
}
export interface CreateRepoResult {
id: string;
name: RepoName;
description: string | null;
default_branch: string;
remote: string;
token: ArtifactToken;
}
export type CreateRepoResponse = ApiEnvelope<CreateRepoResult>;
Terminal window
curl --request POST "$ARTIFACTS_BASE_URL/repos" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"name": "starter-repo",
"description": "Repository for automation experiments",
"default_branch": "main",
"read_only": false
}'
{
"result": {
"id": "repo_123",
"name": "starter-repo",
"description": "Repository for automation experiments",
"default_branch": "main",
"remote": "https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/default/starter-repo.git",
"token": "art_v1_0123456789abcdef0123456789abcdef01234567?expires=1760000000"
},
"success": true,
"errors": [],
"messages": []
}

Create, fork, and import responses return the token string only. The token encodes its expiry directly in the ?expires= suffix. The separate POST /tokens route also returns expires_at alongside the plaintext token.

List repos

Route: GET /artifacts/namespaces/:namespace/repos?limit=&cursor=&search=&sort=&direction=

Query parameters:

  • limit number optional (default: 50, max: 200)
  • cursor Cursor optional
  • search string optional
  • sort "created_at" | "updated_at" | "last_push_at" | "name" optional (default: "created_at")
  • direction "asc" | "desc" optional (default: "desc")

Response type:

TypeScript
export interface ListReposQuery {
limit?: number;
cursor?: Cursor;
search?: string;
sort?: RepoSortField;
direction?: SortDirection;
}
export type ListReposResponse = ApiEnvelope<RepoWithRemote[]>;
Terminal window
curl "$ARTIFACTS_BASE_URL/repos?limit=20&sort=updated_at&direction=desc" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
{
"result": [
{
"id": "repo_123",
"name": "starter-repo",
"description": "Repository for automation experiments",
"default_branch": "main",
"created_at": "<ISO_TIMESTAMP>",
"updated_at": "<ISO_TIMESTAMP>",
"last_push_at": "<ISO_TIMESTAMP>",
"source": null,
"read_only": false,
"remote": "https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/default/starter-repo.git"
}
],
"success": true,
"errors": [],
"messages": [],
"result_info": {
"cursor": "next-cursor",
"per_page": 20,
"count": 1
}
}

Get a repo

Route: GET /artifacts/namespaces/:namespace/repos/:name

Response type:

TypeScript
export type GetRepoResponse = ApiEnvelope<RepoWithRemote>;
Terminal window
curl "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
{
"result": {
"id": "repo_123",
"name": "starter-repo",
"description": "Repository for automation experiments",
"default_branch": "main",
"created_at": "<ISO_TIMESTAMP>",
"updated_at": "<ISO_TIMESTAMP>",
"last_push_at": "<ISO_TIMESTAMP>",
"source": null,
"read_only": false,
"remote": "https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/default/starter-repo.git"
},
"success": true,
"errors": [],
"messages": []
}

Delete a repo

Route: DELETE /artifacts/namespaces/:namespace/repos/:name

This route returns 202 Accepted.

Response type:

TypeScript
export interface DeleteRepoResult {
id: string;
}
export type DeleteRepoResponse = ApiEnvelope<DeleteRepoResult>;
Terminal window
curl --request DELETE "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
{
"result": {
"id": "repo_123"
},
"success": true,
"errors": [],
"messages": []
}

Fork a repo

Route: POST /artifacts/namespaces/:namespace/repos/:name/fork

Request body:

  • name RepoName required
  • description string optional
  • read_only boolean optional
  • default_branch_only boolean optional

Response type:

TypeScript
export interface ForkRepoRequest {
name: RepoName;
description?: string;
read_only?: boolean;
default_branch_only?: boolean;
}
export interface ForkRepoResult extends CreateRepoResult {
objects: number;
}
export type ForkRepoResponse = ApiEnvelope<ForkRepoResult>;
Terminal window
curl --request POST "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO/fork" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"name": "starter-repo-copy",
"description": "Fork for testing",
"read_only": false,
"default_branch_only": true
}'
{
"result": {
"id": "repo_456",
"name": "starter-repo-copy",
"description": "Repository for automation experiments",
"default_branch": "main",
"remote": "https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/default/starter-repo-copy.git",
"token": "art_v1_89abcdef0123456789abcdef0123456789abcdef?expires=1760003600",
"objects": 128
},
"success": true,
"errors": [],
"messages": []
}

Import a public HTTPS remote

Route: POST /artifacts/namespaces/:namespace/repos/:name/import

Request body:

  • url string required
  • branch string optional
  • depth number optional
  • read_only boolean optional

Response type:

TypeScript
export interface ImportRepoRequest {
url: string;
branch?: string;
depth?: number;
read_only?: boolean;
}
export type ImportRepoResponse = ApiEnvelope<CreateRepoResult>;

Pass a full HTTPS Git remote URL, for example https://github.com/facebook/react or https://gitlab.com/group/project.git.

Terminal window
curl --request POST "$ARTIFACTS_BASE_URL/repos/react-mirror/import" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"url": "https://github.com/facebook/react",
"branch": "main",
"depth": 100
}'
{
"result": {
"id": "repo_789",
"name": "react-mirror",
"description": null,
"default_branch": "main",
"remote": "https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/default/react-mirror.git",
"token": "art_v1_fedcba9876543210fedcba9876543210fedcba98?expires=1760007200"
},
"success": true,
"errors": [],
"messages": []
}

If a repo exists but is still importing or forking, this route can return 409 Conflict with a retriable error message.

Repo content

These routes read Git objects and files from an existing repo. Object routes use immutable Git SHA-1 hashes. File routes resolve a path at a branch, tag, or commit hash.

Read commit history

Route: GET /artifacts/namespaces/:namespace/repos/:name/log?ref=&limit=&offset=

Terminal window
curl "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO/log?ref=main&limit=10" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"

Read a commit

Route: GET /artifacts/namespaces/:namespace/repos/:name/commit/:hash

Terminal window
curl "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO/commit/$COMMIT_HASH" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"

Read a tree

Route: GET /artifacts/namespaces/:namespace/repos/:name/tree/:hash

Terminal window
curl "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO/tree/$TREE_HASH" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"

Read a blob

Route: GET /artifacts/namespaces/:namespace/repos/:name/blob/:hash

Returns raw blob bytes.

Terminal window
curl "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO/blob/$BLOB_HASH" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"

Read a file

Route: GET /artifacts/namespaces/:namespace/repos/:name/file?ref=&path=

Returns raw file bytes as application/octet-stream.

Terminal window
curl "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO/file?ref=main&path=README.md" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"

Read a raw file

Route: GET /artifacts/namespaces/:namespace/repos/:name/raw/:ref/:path

Returns file bytes with a sniffed Content-Type.

Terminal window
curl "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO/raw/main/README.md" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"

Tokens

These tokens are for Git routes. They do not authenticate REST API requests.

List tokens for a repo

Route: GET /artifacts/namespaces/:namespace/repos/:name/tokens?state=&per_page=&page=

Query parameters:

  • state "active" | "expired" | "revoked" | "all" optional (default: "active")
  • per_page number optional (default: 30, max: 100)
  • page number optional (default: 1)

Response type:

TypeScript
export interface ListTokensQuery {
state?: TokenState | "all";
per_page?: number;
page?: number;
}
export type ListTokensResponse = ApiEnvelope<TokenInfo[]>;
Terminal window
curl "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO/tokens?state=all&per_page=30&page=1" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
{
"result": [
{
"id": "0123456789abcdef",
"scope": "read",
"state": "active",
"created_at": "<ISO_TIMESTAMP>",
"expires_at": "<ISO_TIMESTAMP>"
}
],
"success": true,
"errors": [],
"messages": [],
"result_info": {
"page": 1,
"per_page": 30,
"total_pages": 1,
"count": 1,
"total_count": 1
}
}

Create a token

Route: POST /artifacts/namespaces/:namespace/tokens

Request body:

  • repo RepoName required
  • scope "read" | "write" optional (default: "write")
  • ttl number optional — Token time-to-live in seconds. Minimum 60 (1 minute), maximum 31,536,000 (1 year). Defaults to 86,400 (24 hours).

Response type:

TypeScript
export interface CreateTokenRequest {
repo: RepoName;
scope?: Scope;
ttl?: number;
}
export interface CreateTokenResult {
id: string;
plaintext: ArtifactToken;
scope: Scope;
expires_at: string;
}
export type CreateTokenResponse = ApiEnvelope<CreateTokenResult>;
Terminal window
curl --request POST "$ARTIFACTS_BASE_URL/tokens" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"repo": "starter-repo",
"scope": "read",
"ttl": 3600
}'
{
"result": {
"id": "0123456789abcdef",
"plaintext": "art_v1_0123456789abcdef0123456789abcdef01234567?expires=1760000000",
"scope": "read",
"expires_at": "<ISO_TIMESTAMP>"
},
"success": true,
"errors": [],
"messages": []
}

Revoke a token

Route: DELETE /artifacts/namespaces/:namespace/tokens/:id

Response type:

TypeScript
export interface DeleteTokenResult {
id: string;
}
export type DeleteTokenResponse = ApiEnvelope<DeleteTokenResult>;
Terminal window
curl --request DELETE "$ARTIFACTS_BASE_URL/tokens/0123456789abcdef" \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
{
"result": {
"id": "0123456789abcdef"
},
"success": true,
"errors": [],
"messages": []
}

Errors

Application errors also use the v4 envelope:

TypeScript
export interface ApiError {
code: number;
message: string;
documentation_url?: string;
source?: {
pointer?: string;
};
}

Next steps